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
18using base::StringPiece;
19using base::TimeDelta;
20
21namespace {
22
23const char kChromeProxyHeader[] = "chrome-proxy";
24const char kActionValueDelimiter = '=';
25
26const char kChromeProxyActionBlockOnce[] = "block-once";
27const char kChromeProxyActionBlock[] = "block";
28const char kChromeProxyActionBypass[] = "bypass";
29
30// Actions for tamper detection fingerprints.
31const char kChromeProxyActionFingerprintChromeProxy[]   = "fcp";
32const char kChromeProxyActionFingerprintVia[]           = "fvia";
33const char kChromeProxyActionFingerprintOtherHeaders[]  = "foh";
34const char kChromeProxyActionFingerprintContentLength[] = "fcl";
35
36const int kShortBypassMaxSeconds = 59;
37const int kMediumBypassMaxSeconds = 300;
38
39// Returns a random bypass duration between 1 and 5 minutes.
40base::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
49namespace data_reduction_proxy {
50
51bool 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
77bool 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
113bool 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
159bool 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
195DataReductionProxyBypassType 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
251bool 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
260bool GetDataReductionProxyActionFingerprintVia(
261    const net::HttpResponseHeaders* headers,
262    std::string* via_fingerprint) {
263  return GetDataReductionProxyActionValue(
264      headers,
265      kChromeProxyActionFingerprintVia,
266      via_fingerprint);
267}
268
269bool 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
278bool 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
287void 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