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