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.
4
5 #include "components/data_reduction_proxy/common/data_reduction_proxy_headers.h"
6
7 #include <string>
8 #include <vector>
9
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"
17
18 using base::StringPiece;
19 using base::TimeDelta;
20
21 namespace {
22
23 const char kChromeProxyHeader[] = "chrome-proxy";
24 const char kActionValueDelimiter = '=';
25
26 const char kChromeProxyActionBlockOnce[] = "block-once";
27 const char kChromeProxyActionBlock[] = "block";
28 const char kChromeProxyActionBypass[] = "bypass";
29
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";
35
36 const int kShortBypassMaxSeconds = 59;
37 const int kMediumBypassMaxSeconds = 300;
38
39 // Returns a random bypass duration between 1 and 5 minutes.
GetDefaultBypassDuration()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);
45 }
46
47 } // namespace
48
49 namespace data_reduction_proxy {
50
GetDataReductionProxyActionValue(const net::HttpResponseHeaders * headers,const std::string & action_prefix,std::string * action_value)51 bool GetDataReductionProxyActionValue(
52 const net::HttpResponseHeaders* headers,
53 const std::string& action_prefix,
54 std::string* action_value) {
55 DCHECK(headers);
56 DCHECK(!action_prefix.empty());
57 // A valid action does not include a trailing '='.
58 DCHECK(action_prefix[action_prefix.size() - 1] != kActionValueDelimiter);
59 void* iter = NULL;
60 std::string value;
61 std::string prefix = action_prefix + kActionValueDelimiter;
62
63 while (headers->EnumerateHeader(&iter, kChromeProxyHeader, &value)) {
64 if (value.size() > prefix.size()) {
65 if (LowerCaseEqualsASCII(value.begin(),
66 value.begin() + prefix.size(),
67 prefix.c_str())) {
68 if (action_value)
69 *action_value = value.substr(prefix.size());
70 return true;
71 }
72 }
73 }
74 return false;
75 }
76
ParseHeadersAndSetBypassDuration(const net::HttpResponseHeaders * headers,const std::string & action_prefix,base::TimeDelta * bypass_duration)77 bool ParseHeadersAndSetBypassDuration(const net::HttpResponseHeaders* headers,
78 const std::string& action_prefix,
79 base::TimeDelta* bypass_duration) {
80 DCHECK(headers);
81 DCHECK(!action_prefix.empty());
82 // A valid action does not include a trailing '='.
83 DCHECK(action_prefix[action_prefix.size() - 1] != kActionValueDelimiter);
84 void* iter = NULL;
85 std::string value;
86 std::string prefix = action_prefix + kActionValueDelimiter;
87
88 while (headers->EnumerateHeader(&iter, kChromeProxyHeader, &value)) {
89 if (value.size() > prefix.size()) {
90 if (LowerCaseEqualsASCII(value.begin(),
91 value.begin() + prefix.size(),
92 prefix.c_str())) {
93 int64 seconds;
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.
98 }
99 if (seconds != 0) {
100 *bypass_duration = TimeDelta::FromSeconds(seconds);
101 } else {
102 // Server deferred to us to choose a duration. Default to a random
103 // duration between one and five minutes.
104 *bypass_duration = GetDefaultBypassDuration();
105 }
106 return true;
107 }
108 }
109 }
110 return false;
111 }
112
ParseHeadersAndSetProxyInfo(const net::HttpResponseHeaders * headers,DataReductionProxyInfo * proxy_info)113 bool ParseHeadersAndSetProxyInfo(const net::HttpResponseHeaders* headers,
114 DataReductionProxyInfo* proxy_info) {
115 DCHECK(proxy_info);
116
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.
124
125 // 'block' takes precedence over 'bypass' and 'block-once', so look for it
126 // first.
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;
132 return true;
133 }
134
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;
140 return true;
141 }
142
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();
153 return true;
154 }
155
156 return false;
157 }
158
HasDataReductionProxyViaHeader(const net::HttpResponseHeaders * headers,bool * has_intermediary)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);
164 void* iter = NULL;
165 std::string value;
166
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));
177 return true;
178 }
179 }
180
181 // TODO(bengr): Remove deprecated header value.
182 const char kDeprecatedDataReductionProxyViaValue[] =
183 "1.1 Chrome Compression Proxy";
184 iter = NULL;
185 while (headers->EnumerateHeader(&iter, "via", &value))
186 if (value == kDeprecatedDataReductionProxyViaValue) {
187 if (has_intermediary)
188 *has_intermediary = !(headers->EnumerateHeader(&iter, "via", &value));
189 return true;
190 }
191
192 return false;
193 }
194
GetDataReductionProxyBypassType(const net::HttpResponseHeaders * headers,DataReductionProxyInfo * data_reduction_proxy_info)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;
204
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;
211 }
212
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
215 // minutes.
216 data_reduction_proxy_info->mark_proxies_as_bad = true;
217 data_reduction_proxy_info->bypass_duration = GetDefaultBypassDuration();
218
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;
231 }
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.
238
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;
244 }
245 return BYPASS_EVENT_TYPE_MISSING_VIA_HEADER_OTHER;
246 }
247 // There is no bypass event.
248 return BYPASS_EVENT_TYPE_MAX;
249 }
250
GetDataReductionProxyActionFingerprintChromeProxy(const net::HttpResponseHeaders * headers,std::string * chrome_proxy_fingerprint)251 bool GetDataReductionProxyActionFingerprintChromeProxy(
252 const net::HttpResponseHeaders* headers,
253 std::string* chrome_proxy_fingerprint) {
254 return GetDataReductionProxyActionValue(
255 headers,
256 kChromeProxyActionFingerprintChromeProxy,
257 chrome_proxy_fingerprint);
258 }
259
GetDataReductionProxyActionFingerprintVia(const net::HttpResponseHeaders * headers,std::string * via_fingerprint)260 bool GetDataReductionProxyActionFingerprintVia(
261 const net::HttpResponseHeaders* headers,
262 std::string* via_fingerprint) {
263 return GetDataReductionProxyActionValue(
264 headers,
265 kChromeProxyActionFingerprintVia,
266 via_fingerprint);
267 }
268
GetDataReductionProxyActionFingerprintOtherHeaders(const net::HttpResponseHeaders * headers,std::string * other_headers_fingerprint)269 bool GetDataReductionProxyActionFingerprintOtherHeaders(
270 const net::HttpResponseHeaders* headers,
271 std::string* other_headers_fingerprint) {
272 return GetDataReductionProxyActionValue(
273 headers,
274 kChromeProxyActionFingerprintOtherHeaders,
275 other_headers_fingerprint);
276 }
277
GetDataReductionProxyActionFingerprintContentLength(const net::HttpResponseHeaders * headers,std::string * content_length_fingerprint)278 bool GetDataReductionProxyActionFingerprintContentLength(
279 const net::HttpResponseHeaders* headers,
280 std::string* content_length_fingerprint) {
281 return GetDataReductionProxyActionValue(
282 headers,
283 kChromeProxyActionFingerprintContentLength,
284 content_length_fingerprint);
285 }
286
GetDataReductionProxyHeaderWithFingerprintRemoved(const net::HttpResponseHeaders * headers,std::vector<std::string> * values)287 void GetDataReductionProxyHeaderWithFingerprintRemoved(
288 const net::HttpResponseHeaders* headers,
289 std::vector<std::string>* values) {
290 DCHECK(values);
291 std::string chrome_proxy_fingerprint_prefix = std::string(
292 kChromeProxyActionFingerprintChromeProxy) + kActionValueDelimiter;
293
294 std::string value;
295 void* iter = NULL;
296 while (headers->EnumerateHeader(&iter, kChromeProxyHeader, &value)) {
297 if (value.size() > chrome_proxy_fingerprint_prefix.size()) {
298 if (LowerCaseEqualsASCII(
299 value.begin(),
300 value.begin() + chrome_proxy_fingerprint_prefix.size(),
301 chrome_proxy_fingerprint_prefix.c_str())) {
302 continue;
303 }
304 }
305 values->push_back(value);
306 }
307 }
308
309 } // namespace data_reduction_proxy
310