1 /*
2  * Copyright (c) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "net_http_cache_strategy.h"
17 
18 #include <algorithm>
19 #include <cstring>
20 
21 #include "constant.h"
22 #include "net_http_utils.h"
23 #include "netstack_log.h"
24 
25 static constexpr int64_t ONE_DAY_MILLISECONDS = 24 * 60 * 60 * 1000L;
26 static constexpr int64_t CONVERT_TO_MILLISECONDS = 1000;
27 static constexpr const char *KEY_RANGE = "range";
28 
29 // RFC 7234
30 
31 namespace OHOS::NetStack::Http {
HttpCacheStrategy(HttpRequest & requestOptions)32 HttpCacheStrategy::HttpCacheStrategy(HttpRequest &requestOptions) : requestOptions_(requestOptions)
33 {
34     cacheRequest_.ParseRequestHeader(requestOptions_.GetHeader());
35     cacheRequest_.SetRequestTime(requestOptions_.GetRequestTime());
36 }
37 
CouldUseCache()38 bool HttpCacheStrategy::CouldUseCache()
39 {
40     return requestOptions_.GetMethod() == HTTP_METHOD_GET ||
41            requestOptions_.GetMethod() == HTTP_METHOD_HEAD ||
42            requestOptions_.GetHeader().find(KEY_RANGE) != requestOptions_.GetHeader().end();
43 }
44 
RunStrategy(HttpResponse & response)45 CacheStatus HttpCacheStrategy::RunStrategy(HttpResponse &response)
46 {
47     cacheResponse_.ParseCacheResponseHeader(response.GetHeader());
48     cacheResponse_.SetRespCode(static_cast<ResponseCode>(response.GetResponseCode()));
49     cacheResponse_.SetResponseTime(response.GetResponseTime());
50     cacheResponse_.SetRequestTime(response.GetRequestTime());
51     return RunStrategyInternal(response);
52 }
53 
IsCacheable(const HttpResponse & response)54 bool HttpCacheStrategy::IsCacheable(const HttpResponse &response)
55 {
56     if (!CouldUseCache()) {
57         return false;
58     }
59     HttpCacheResponse tempCacheResponse;
60     tempCacheResponse.ParseCacheResponseHeader(response.GetHeader());
61     tempCacheResponse.SetRespCode(static_cast<ResponseCode>(response.GetResponseCode()));
62     return IsCacheable(tempCacheResponse);
63 }
64 
CacheResponseAgeMillis()65 int64_t HttpCacheStrategy::CacheResponseAgeMillis()
66 {
67     // 4.2.3. Calculating Age
68 
69     /* The following data is used for the age calculation:
70 
71     age_value
72     The term "age_value" denotes the value of the Age header field (Section 5.1), in a form appropriate for arithmetic
73     operation; or 0, if not available.
74 
75     date_value
76     The term "date_value" denotes the value of the Date header field, in a form appropriate for arithmetic operations.
77     See Section 7.1.1.2 of [RFC7231] for the definition of the Date header field, and for requirements regarding
78     responses without it.
79 
80     now
81     The term "now" means "the current value of the clock at the host performing the calculation". A host ought to use
82     NTP ([RFC5905]) or some similar protocol to synchronize its clocks to Coordinated Universal Time.
83 
84     request_time
85     The current value of the clock at the host at the time the request resulting in the stored response was made.
86 
87     response_time
88     The current value of the clock at the host at the time the response was received.
89     A response's age can be calculated in two entirely independent ways:
90 
91     the "apparent_age": response_time minus date_value,
92     if the local clock is reasonably well synchronized to the origin
93     server's clock. If the result is negative, the result is replaced by zero. the "corrected_age_value", if all of the
94     caches along the response path implement HTTP/1.1. A cache MUST interpret this value relative to the time the
95     request was initiated, not the time that the response was received. apparent_age = max(0, response_time -
96     date_value);
97 
98     response_delay = response_time - request_time;
99     corrected_age_value = age_value + response_delay;
100     These are combined as
101 
102     corrected_initial_age = max(apparent_age, corrected_age_value);
103     unless the cache is confident in the value of the Age header field (e.g., because there are no HTTP/1.0 hops in the
104     Via header field), in which case the corrected_age_value MAY be used as the corrected_initial_age.
105 
106     The current_age of a stored response can then be calculated by adding the amount of time (in seconds) since the
107     stored response was last validated by the origin server to the corrected_initial_age.
108 
109     resident_time = now - response_time;
110     current_age = corrected_initial_age + resident_time; */
111 
112     int64_t age = std::max<int64_t>(0, cacheResponse_.GetAgeSeconds());
113     int64_t dateTime = std::max<int64_t>(0, cacheResponse_.GetDate());
114     int64_t nowTime = std::max<int64_t>(0, GetNowTimeSeconds());
115     int64_t requestTime = std::max<int64_t>(0, cacheResponse_.GetRequestTime());
116     int64_t responseTime = std::max<int64_t>(0, cacheResponse_.GetResponseTime());
117 
118     NETSTACK_LOGI("CacheResponseAgeMillis: %{public}lld, %{public}lld, %{public}lld, %{public}lld, %{public}lld",
119         static_cast<long long>(age), static_cast<long long>(dateTime), static_cast<long long>(nowTime),
120         static_cast<long long>(requestTime), static_cast<long long>(responseTime));
121 
122     int64_t apparentAge = std::max<int64_t>(0, responseTime - dateTime);
123     int64_t responseDelay = std::max<int64_t>(0, responseTime - requestTime);
124     int64_t correctedAgeValue = age + responseDelay;
125     int64_t correctedInitialAge = std::max<int64_t>(apparentAge, correctedAgeValue);
126 
127     int64_t residentTime = std::max<int64_t>(0, nowTime - responseTime);
128 
129     return (correctedInitialAge + residentTime) * CONVERT_TO_MILLISECONDS;
130 }
131 
ComputeFreshnessLifetimeSecondsInternal()132 int64_t HttpCacheStrategy::ComputeFreshnessLifetimeSecondsInternal()
133 {
134     int64_t sMaxAge = cacheResponse_.GetSMaxAgeSeconds();
135     if (sMaxAge != INVALID_TIME) {
136         // If the cache is shared and the s-maxage response directive (Section 5.2.2.9) is present, use its value
137         return sMaxAge;
138     }
139 
140     int64_t maxAge = cacheResponse_.GetMaxAgeSeconds();
141     if (maxAge != INVALID_TIME) {
142         // If the max-age response directive (Section 5.2.2.8) is present, use its value
143         return maxAge;
144     }
145 
146     if (cacheResponse_.GetExpires() != INVALID_TIME) {
147         // If the Expires response header field (Section 5.3) is present, use its value minus the value of the Date
148         // response header field
149         int64_t responseTime = cacheResponse_.GetResponseTime();
150         if (cacheResponse_.GetDate() != INVALID_TIME) {
151             responseTime = cacheResponse_.GetDate();
152         }
153         int64_t delta = cacheResponse_.GetExpires() - responseTime;
154         return std::max<int64_t>(delta, 0);
155     }
156 
157     if (cacheResponse_.GetLastModified() != INVALID_TIME) {
158         // 4.2.2. Calculating Heuristic Freshness
159         int64_t requestTime = cacheRequest_.GetRequestTime();
160         if (cacheResponse_.GetDate() != INVALID_TIME) {
161             requestTime = cacheResponse_.GetDate();
162         }
163         int64_t delta = requestTime - cacheResponse_.GetLastModified();
164         return std::max<int64_t>(delta / DECIMAL, 0);
165     }
166 
167     return 0;
168 }
169 
ComputeFreshnessLifetimeMillis()170 int64_t HttpCacheStrategy::ComputeFreshnessLifetimeMillis()
171 {
172     int64_t lifeTime = ComputeFreshnessLifetimeSecondsInternal();
173 
174     int64_t reqMaxAge = cacheRequest_.GetMaxAgeSeconds();
175     if (reqMaxAge != INVALID_TIME) {
176         lifeTime = std::min(lifeTime, reqMaxAge);
177     }
178 
179     NETSTACK_LOGI("lifeTime=%{public}lld", static_cast<long long>(lifeTime));
180     return lifeTime * CONVERT_TO_MILLISECONDS;
181 }
182 
UpdateRequestHeader(const std::string & etag,const std::string & lastModified,const std::string & date)183 void HttpCacheStrategy::UpdateRequestHeader(const std::string &etag,
184                                             const std::string &lastModified,
185                                             const std::string &date)
186 {
187     if (!etag.empty()) {
188         requestOptions_.SetHeader(IF_NONE_MATCH, etag);
189     } else if (!lastModified.empty()) {
190         requestOptions_.SetHeader(IF_MODIFIED_SINCE, lastModified);
191     } else if (!date.empty()) {
192         requestOptions_.SetHeader(IF_MODIFIED_SINCE, date);
193     }
194 }
195 
IsCacheable(const HttpCacheResponse & cacheResponse)196 bool HttpCacheStrategy::IsCacheable(const HttpCacheResponse &cacheResponse)
197 {
198     switch (cacheResponse.GetRespCode()) {
199         case ResponseCode::OK:
200         case ResponseCode::NOT_AUTHORITATIVE:
201         case ResponseCode::NO_CONTENT:
202         case ResponseCode::MULT_CHOICE:
203         case ResponseCode::MOVED_PERM:
204         case ResponseCode::NOT_FOUND:
205         case ResponseCode::BAD_METHOD:
206         case ResponseCode::GONE:
207         case ResponseCode::REQ_TOO_LONG:
208         case ResponseCode::NOT_IMPLEMENTED:
209             // These codes can be cached unless headers forbid it.
210             break;
211 
212         case ResponseCode::MOVED_TEMP:
213             if (cacheResponse.GetExpires() != INVALID_TIME || cacheResponse.GetMaxAgeSeconds() != INVALID_TIME ||
214                 cacheResponse.IsPublicCache() || cacheResponse.IsPrivateCache()) {
215                 break;
216             }
217             return false;
218 
219         default:
220             return false;
221     }
222 
223     return !cacheResponse.IsNoStore() && !cacheRequest_.IsNoStore();
224 }
225 
GetFreshness()226 std::tuple<int64_t, int64_t, int64_t, int64_t> HttpCacheStrategy::GetFreshness()
227 {
228     int64_t ageMillis = CacheResponseAgeMillis();
229 
230     int64_t lifeTime = ComputeFreshnessLifetimeMillis();
231 
232     int64_t minFreshMillis = std::max<int64_t>(0, cacheRequest_.GetMinFreshSeconds() * CONVERT_TO_MILLISECONDS);
233 
234     int64_t maxStaleMillis = 0;
235     if (!cacheResponse_.IsMustRevalidate()) {
236         maxStaleMillis = std::max<int64_t>(0, cacheRequest_.GetMaxStaleSeconds() * CONVERT_TO_MILLISECONDS);
237     }
238 
239     NETSTACK_LOGI("GetFreshness: %{public}lld, %{public}lld, %{public}lld, %{public}lld",
240         static_cast<long long>(ageMillis), static_cast<long long>(minFreshMillis),
241         static_cast<long long>(lifeTime), static_cast<long long>(maxStaleMillis));
242     return {ageMillis, minFreshMillis, lifeTime, maxStaleMillis};
243 }
244 
RunStrategyInternal(HttpResponse & response)245 CacheStatus HttpCacheStrategy::RunStrategyInternal(HttpResponse &response)
246 {
247     if (cacheRequest_.IsNoCache()) {
248         NETSTACK_LOGI("return DENY");
249         return DENY;
250     }
251 
252     if (cacheResponse_.IsNoCache()) {
253         NETSTACK_LOGI("return STALE");
254         return STALE;
255     }
256 
257     if (cacheRequest_.IsOnlyIfCached()) {
258         NETSTACK_LOGI("return FRESH");
259         return FRESH;
260     }
261 
262     // T.B.D https and TLS handshake
263     if (!IsCacheable(cacheResponse_)) {
264         NETSTACK_LOGI("return DENY");
265         return DENY;
266     }
267 
268     if (cacheRequest_.GetIfModifiedSince() != INVALID_TIME || !cacheRequest_.GetIfNoneMatch().empty()) {
269         NETSTACK_LOGI("return DENY");
270         return DENY;
271     }
272 
273     auto [ageMillis, minFreshMillis, lifeTime, maxStaleMillis] = GetFreshness();
274 
275     if (ageMillis + minFreshMillis < lifeTime + maxStaleMillis) {
276         if (ageMillis + minFreshMillis >= lifeTime) {
277             response.SetWarning("110 \"Response is STALE\"");
278         }
279 
280         if (ageMillis > ONE_DAY_MILLISECONDS && cacheRequest_.GetMaxAgeSeconds() == INVALID_TIME &&
281             cacheResponse_.GetExpires() == INVALID_TIME) {
282             response.SetWarning("113 \"Heuristic expiration\"");
283         }
284 
285         NETSTACK_LOGI("return FRESH");
286         return FRESH;
287     }
288 
289     // The cache has expired and the request needs to be re-initialized
290     UpdateRequestHeader(cacheResponse_.GetEtag(), cacheResponse_.GetLastModifiedStr(), cacheResponse_.GetDateStr());
291 
292     NETSTACK_LOGI("return STALE");
293     return STALE;
294 }
295 } // namespace OHOS::NetStack::Http
296