1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/data_reduction_proxy/common/data_reduction_proxy_headers.h"
10 #include "base/rand_util.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/string_piece.h"
13 #include "base/strings/string_util.h"
14 #include "base/time/time.h"
15 #include "net/http/http_response_headers.h"
16 #include "net/http/http_status_code.h"
18 using base::StringPiece;
19 using base::TimeDelta;
23 const char kChromeProxyHeader[] = "chrome-proxy";
24 const char kActionValueDelimiter = '=';
26 const char kChromeProxyActionBlockOnce[] = "block-once";
27 const char kChromeProxyActionBlock[] = "block";
28 const char kChromeProxyActionBypass[] = "bypass";
30 // Actions for tamper detection fingerprints.
31 const char kChromeProxyActionFingerprintChromeProxy[] = "fcp";
32 const char kChromeProxyActionFingerprintVia[] = "fvia";
33 const char kChromeProxyActionFingerprintOtherHeaders[] = "foh";
34 const char kChromeProxyActionFingerprintContentLength[] = "fcl";
36 const int kShortBypassMaxSeconds = 59;
37 const int kMediumBypassMaxSeconds = 300;
39 // Returns a random bypass duration between 1 and 5 minutes.
40 base::TimeDelta GetDefaultBypassDuration() {
41 const int64 delta_ms =
42 base::RandInt(base::TimeDelta::FromMinutes(1).InMilliseconds(),
43 base::TimeDelta::FromMinutes(5).InMilliseconds());
44 return TimeDelta::FromMilliseconds(delta_ms);
49 namespace data_reduction_proxy {
51 bool GetDataReductionProxyActionValue(
52 const net::HttpResponseHeaders* headers,
53 const std::string& action_prefix,
54 std::string* action_value) {
56 DCHECK(!action_prefix.empty());
57 // A valid action does not include a trailing '='.
58 DCHECK(action_prefix[action_prefix.size() - 1] != kActionValueDelimiter);
61 std::string prefix = action_prefix + kActionValueDelimiter;
63 while (headers->EnumerateHeader(&iter, kChromeProxyHeader, &value)) {
64 if (value.size() > prefix.size()) {
65 if (LowerCaseEqualsASCII(value.begin(),
66 value.begin() + prefix.size(),
69 *action_value = value.substr(prefix.size());
77 bool ParseHeadersAndSetBypassDuration(const net::HttpResponseHeaders* headers,
78 const std::string& action_prefix,
79 base::TimeDelta* bypass_duration) {
81 DCHECK(!action_prefix.empty());
82 // A valid action does not include a trailing '='.
83 DCHECK(action_prefix[action_prefix.size() - 1] != kActionValueDelimiter);
86 std::string prefix = action_prefix + kActionValueDelimiter;
88 while (headers->EnumerateHeader(&iter, kChromeProxyHeader, &value)) {
89 if (value.size() > prefix.size()) {
90 if (LowerCaseEqualsASCII(value.begin(),
91 value.begin() + prefix.size(),
94 if (!base::StringToInt64(
95 StringPiece(value.begin() + prefix.size(), value.end()),
96 &seconds) || seconds < 0) {
97 continue; // In case there is a well formed instruction.
100 *bypass_duration = TimeDelta::FromSeconds(seconds);
102 // Server deferred to us to choose a duration. Default to a random
103 // duration between one and five minutes.
104 *bypass_duration = GetDefaultBypassDuration();
113 bool ParseHeadersAndSetProxyInfo(const net::HttpResponseHeaders* headers,
114 DataReductionProxyInfo* proxy_info) {
117 // Support header of the form Chrome-Proxy: bypass|block=<duration>, where
118 // <duration> is the number of seconds to wait before retrying
119 // the proxy. If the duration is 0, then the default proxy retry delay
120 // (specified in |ProxyList::UpdateRetryInfoOnFallback|) will be used.
121 // 'bypass' instructs Chrome to bypass the currently connected data reduction
122 // proxy, whereas 'block' instructs Chrome to bypass all available data
123 // reduction proxies.
125 // 'block' takes precedence over 'bypass' and 'block-once', so look for it
127 // TODO(bengr): Reduce checks for 'block' and 'bypass' to a single loop.
128 if (ParseHeadersAndSetBypassDuration(
129 headers, kChromeProxyActionBlock, &proxy_info->bypass_duration)) {
130 proxy_info->bypass_all = true;
131 proxy_info->mark_proxies_as_bad = true;
135 // Next, look for 'bypass'.
136 if (ParseHeadersAndSetBypassDuration(
137 headers, kChromeProxyActionBypass, &proxy_info->bypass_duration)) {
138 proxy_info->bypass_all = false;
139 proxy_info->mark_proxies_as_bad = true;
143 // Lastly, look for 'block-once'. 'block-once' instructs Chrome to retry the
144 // current request (if it's idempotent), bypassing all available data
145 // reduction proxies. Unlike 'block', 'block-once' does not cause data
146 // reduction proxies to be bypassed for an extended period of time;
147 // 'block-once' only affects the retry of the current request.
148 if (headers->HasHeaderValue(kChromeProxyHeader,
149 kChromeProxyActionBlockOnce)) {
150 proxy_info->bypass_all = true;
151 proxy_info->mark_proxies_as_bad = false;
152 proxy_info->bypass_duration = TimeDelta();
159 bool HasDataReductionProxyViaHeader(const net::HttpResponseHeaders* headers,
160 bool* has_intermediary) {
161 const size_t kVersionSize = 4;
162 const char kDataReductionProxyViaValue[] = "Chrome-Compression-Proxy";
163 size_t value_len = strlen(kDataReductionProxyViaValue);
167 // Case-sensitive comparison of |value|. Assumes the received protocol and the
168 // space following it are always |kVersionSize| characters. E.g.,
169 // 'Via: 1.1 Chrome-Compression-Proxy'
170 while (headers->EnumerateHeader(&iter, "via", &value)) {
171 if (value.size() >= kVersionSize + value_len &&
172 !value.compare(kVersionSize, value_len, kDataReductionProxyViaValue)) {
173 if (has_intermediary)
174 // We assume intermediary exists if there is another Via header after
175 // the data reduction proxy's Via header.
176 *has_intermediary = !(headers->EnumerateHeader(&iter, "via", &value));
181 // TODO(bengr): Remove deprecated header value.
182 const char kDeprecatedDataReductionProxyViaValue[] =
183 "1.1 Chrome Compression Proxy";
185 while (headers->EnumerateHeader(&iter, "via", &value))
186 if (value == kDeprecatedDataReductionProxyViaValue) {
187 if (has_intermediary)
188 *has_intermediary = !(headers->EnumerateHeader(&iter, "via", &value));
195 DataReductionProxyBypassType GetDataReductionProxyBypassType(
196 const net::HttpResponseHeaders* headers,
197 DataReductionProxyInfo* data_reduction_proxy_info) {
198 DCHECK(data_reduction_proxy_info);
199 if (ParseHeadersAndSetProxyInfo(headers, data_reduction_proxy_info)) {
200 // A chrome-proxy response header is only present in a 502. For proper
201 // reporting, this check must come before the 5xx checks below.
202 if (!data_reduction_proxy_info->mark_proxies_as_bad)
203 return BYPASS_EVENT_TYPE_CURRENT;
205 const TimeDelta& duration = data_reduction_proxy_info->bypass_duration;
206 if (duration <= TimeDelta::FromSeconds(kShortBypassMaxSeconds))
207 return BYPASS_EVENT_TYPE_SHORT;
208 if (duration <= TimeDelta::FromSeconds(kMediumBypassMaxSeconds))
209 return BYPASS_EVENT_TYPE_MEDIUM;
210 return BYPASS_EVENT_TYPE_LONG;
213 // If a bypass is triggered by any of the following cases, then the data
214 // reduction proxy should be bypassed for a random duration between 1 and 5
216 data_reduction_proxy_info->mark_proxies_as_bad = true;
217 data_reduction_proxy_info->bypass_duration = GetDefaultBypassDuration();
219 // Fall back if a 500, 502 or 503 is returned.
220 if (headers->response_code() == net::HTTP_INTERNAL_SERVER_ERROR)
221 return BYPASS_EVENT_TYPE_STATUS_500_HTTP_INTERNAL_SERVER_ERROR;
222 if (headers->response_code() == net::HTTP_BAD_GATEWAY)
223 return BYPASS_EVENT_TYPE_STATUS_502_HTTP_BAD_GATEWAY;
224 if (headers->response_code() == net::HTTP_SERVICE_UNAVAILABLE)
225 return BYPASS_EVENT_TYPE_STATUS_503_HTTP_SERVICE_UNAVAILABLE;
226 // TODO(kundaji): Bypass if Proxy-Authenticate header value cannot be
227 // interpreted by data reduction proxy.
228 if (headers->response_code() == net::HTTP_PROXY_AUTHENTICATION_REQUIRED &&
229 !headers->HasHeader("Proxy-Authenticate")) {
230 return BYPASS_EVENT_TYPE_MALFORMED_407;
232 if (!HasDataReductionProxyViaHeader(headers, NULL) &&
233 (headers->response_code() != net::HTTP_NOT_MODIFIED)) {
234 // A Via header might not be present in a 304. Since the goal of a 304
235 // response is to minimize information transfer, a sender in general
236 // should not generate representation metadata other than Cache-Control,
237 // Content-Location, Date, ETag, Expires, and Vary.
239 // The proxy Via header might also not be present in a 4xx response.
240 // Separate this case from other responses that are missing the header.
241 if (headers->response_code() >= net::HTTP_BAD_REQUEST &&
242 headers->response_code() < net::HTTP_INTERNAL_SERVER_ERROR) {
243 return BYPASS_EVENT_TYPE_MISSING_VIA_HEADER_4XX;
245 return BYPASS_EVENT_TYPE_MISSING_VIA_HEADER_OTHER;
247 // There is no bypass event.
248 return BYPASS_EVENT_TYPE_MAX;
251 bool GetDataReductionProxyActionFingerprintChromeProxy(
252 const net::HttpResponseHeaders* headers,
253 std::string* chrome_proxy_fingerprint) {
254 return GetDataReductionProxyActionValue(
256 kChromeProxyActionFingerprintChromeProxy,
257 chrome_proxy_fingerprint);
260 bool GetDataReductionProxyActionFingerprintVia(
261 const net::HttpResponseHeaders* headers,
262 std::string* via_fingerprint) {
263 return GetDataReductionProxyActionValue(
265 kChromeProxyActionFingerprintVia,
269 bool GetDataReductionProxyActionFingerprintOtherHeaders(
270 const net::HttpResponseHeaders* headers,
271 std::string* other_headers_fingerprint) {
272 return GetDataReductionProxyActionValue(
274 kChromeProxyActionFingerprintOtherHeaders,
275 other_headers_fingerprint);
278 bool GetDataReductionProxyActionFingerprintContentLength(
279 const net::HttpResponseHeaders* headers,
280 std::string* content_length_fingerprint) {
281 return GetDataReductionProxyActionValue(
283 kChromeProxyActionFingerprintContentLength,
284 content_length_fingerprint);
287 void GetDataReductionProxyHeaderWithFingerprintRemoved(
288 const net::HttpResponseHeaders* headers,
289 std::vector<std::string>* values) {
291 std::string chrome_proxy_fingerprint_prefix = std::string(
292 kChromeProxyActionFingerprintChromeProxy) + kActionValueDelimiter;
296 while (headers->EnumerateHeader(&iter, kChromeProxyHeader, &value)) {
297 if (value.size() > chrome_proxy_fingerprint_prefix.size()) {
298 if (LowerCaseEqualsASCII(
300 value.begin() + chrome_proxy_fingerprint_prefix.size(),
301 chrome_proxy_fingerprint_prefix.c_str())) {
305 values->push_back(value);
309 } // namespace data_reduction_proxy