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