#include "components/data_reduction_proxy/common/data_reduction_proxy_headers.h"
#include <string>
+#include <vector>
+#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
-#include "net/proxy/proxy_service.h"
using base::StringPiece;
using base::TimeDelta;
-using net::ProxyService;
+
+namespace {
+
+const char kChromeProxyHeader[] = "chrome-proxy";
+const char kActionValueDelimiter = '=';
+
+const char kChromeProxyActionBlockOnce[] = "block-once";
+const char kChromeProxyActionBlock[] = "block";
+const char kChromeProxyActionBypass[] = "bypass";
+
+// Actions for tamper detection fingerprints.
+const char kChromeProxyActionFingerprintChromeProxy[] = "fcp";
+const char kChromeProxyActionFingerprintVia[] = "fvia";
+const char kChromeProxyActionFingerprintOtherHeaders[] = "foh";
+const char kChromeProxyActionFingerprintContentLength[] = "fcl";
+
+const int kShortBypassMaxSeconds = 59;
+const int kMediumBypassMaxSeconds = 300;
+
+// Returns a random bypass duration between 1 and 5 minutes.
+base::TimeDelta GetDefaultBypassDuration() {
+ const int64 delta_ms =
+ base::RandInt(base::TimeDelta::FromMinutes(1).InMilliseconds(),
+ base::TimeDelta::FromMinutes(5).InMilliseconds());
+ return TimeDelta::FromMilliseconds(delta_ms);
+}
+
+} // namespace
namespace data_reduction_proxy {
-bool GetDataReductionProxyBypassDuration(
+bool GetDataReductionProxyActionValue(
const net::HttpResponseHeaders* headers,
const std::string& action_prefix,
- base::TimeDelta* duration) {
+ std::string* action_value) {
+ DCHECK(headers);
+ DCHECK(!action_prefix.empty());
+ // A valid action does not include a trailing '='.
+ DCHECK(action_prefix[action_prefix.size() - 1] != kActionValueDelimiter);
void* iter = NULL;
std::string value;
- std::string name = "chrome-proxy";
+ std::string prefix = action_prefix + kActionValueDelimiter;
- while (headers->EnumerateHeader(&iter, name, &value)) {
- if (value.size() > action_prefix.size()) {
+ while (headers->EnumerateHeader(&iter, kChromeProxyHeader, &value)) {
+ if (value.size() > prefix.size()) {
if (LowerCaseEqualsASCII(value.begin(),
- value.begin() + action_prefix.size(),
- action_prefix.c_str())) {
+ value.begin() + prefix.size(),
+ prefix.c_str())) {
+ if (action_value)
+ *action_value = value.substr(prefix.size());
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool ParseHeadersAndSetBypassDuration(const net::HttpResponseHeaders* headers,
+ const std::string& action_prefix,
+ base::TimeDelta* bypass_duration) {
+ DCHECK(headers);
+ DCHECK(!action_prefix.empty());
+ // A valid action does not include a trailing '='.
+ DCHECK(action_prefix[action_prefix.size() - 1] != kActionValueDelimiter);
+ void* iter = NULL;
+ std::string value;
+ std::string prefix = action_prefix + kActionValueDelimiter;
+
+ while (headers->EnumerateHeader(&iter, kChromeProxyHeader, &value)) {
+ if (value.size() > prefix.size()) {
+ if (LowerCaseEqualsASCII(value.begin(),
+ value.begin() + prefix.size(),
+ prefix.c_str())) {
int64 seconds;
if (!base::StringToInt64(
- StringPiece(value.begin() + action_prefix.size(), value.end()),
+ StringPiece(value.begin() + prefix.size(), value.end()),
&seconds) || seconds < 0) {
continue; // In case there is a well formed instruction.
}
- *duration = TimeDelta::FromSeconds(seconds);
+ if (seconds != 0) {
+ *bypass_duration = TimeDelta::FromSeconds(seconds);
+ } else {
+ // Server deferred to us to choose a duration. Default to a random
+ // duration between one and five minutes.
+ *bypass_duration = GetDefaultBypassDuration();
+ }
return true;
}
}
return false;
}
-bool GetDataReductionProxyInfo(const net::HttpResponseHeaders* headers,
- DataReductionProxyInfo* proxy_info) {
+bool ParseHeadersAndSetProxyInfo(const net::HttpResponseHeaders* headers,
+ DataReductionProxyInfo* proxy_info) {
DCHECK(proxy_info);
- proxy_info->bypass_all = false;
- proxy_info->bypass_duration = TimeDelta();
+
// Support header of the form Chrome-Proxy: bypass|block=<duration>, where
// <duration> is the number of seconds to wait before retrying
// the proxy. If the duration is 0, then the default proxy retry delay
// proxy, whereas 'block' instructs Chrome to bypass all available data
// reduction proxies.
- // 'block' takes precedence over 'bypass', so look for it first.
+ // 'block' takes precedence over 'bypass' and 'block-once', so look for it
+ // first.
// TODO(bengr): Reduce checks for 'block' and 'bypass' to a single loop.
- if (GetDataReductionProxyBypassDuration(
- headers, "block=", &proxy_info->bypass_duration)) {
+ if (ParseHeadersAndSetBypassDuration(
+ headers, kChromeProxyActionBlock, &proxy_info->bypass_duration)) {
proxy_info->bypass_all = true;
+ proxy_info->mark_proxies_as_bad = true;
return true;
}
// Next, look for 'bypass'.
- if (GetDataReductionProxyBypassDuration(
- headers, "bypass=", &proxy_info->bypass_duration)) {
+ if (ParseHeadersAndSetBypassDuration(
+ headers, kChromeProxyActionBypass, &proxy_info->bypass_duration)) {
+ proxy_info->bypass_all = false;
+ proxy_info->mark_proxies_as_bad = true;
return true;
}
+
+ // Lastly, look for 'block-once'. 'block-once' instructs Chrome to retry the
+ // current request (if it's idempotent), bypassing all available data
+ // reduction proxies. Unlike 'block', 'block-once' does not cause data
+ // reduction proxies to be bypassed for an extended period of time;
+ // 'block-once' only affects the retry of the current request.
+ if (headers->HasHeaderValue(kChromeProxyHeader,
+ kChromeProxyActionBlockOnce)) {
+ proxy_info->bypass_all = true;
+ proxy_info->mark_proxies_as_bad = false;
+ proxy_info->bypass_duration = TimeDelta();
+ return true;
+ }
+
return false;
}
-bool HasDataReductionProxyViaHeader(const net::HttpResponseHeaders* headers) {
+bool HasDataReductionProxyViaHeader(const net::HttpResponseHeaders* headers,
+ bool* has_intermediary) {
const size_t kVersionSize = 4;
const char kDataReductionProxyViaValue[] = "Chrome-Compression-Proxy";
size_t value_len = strlen(kDataReductionProxyViaValue);
// 'Via: 1.1 Chrome-Compression-Proxy'
while (headers->EnumerateHeader(&iter, "via", &value)) {
if (value.size() >= kVersionSize + value_len &&
- !value.compare(kVersionSize, value_len, kDataReductionProxyViaValue))
+ !value.compare(kVersionSize, value_len, kDataReductionProxyViaValue)) {
+ if (has_intermediary)
+ // We assume intermediary exists if there is another Via header after
+ // the data reduction proxy's Via header.
+ *has_intermediary = !(headers->EnumerateHeader(&iter, "via", &value));
return true;
+ }
}
// TODO(bengr): Remove deprecated header value.
"1.1 Chrome Compression Proxy";
iter = NULL;
while (headers->EnumerateHeader(&iter, "via", &value))
- if (value == kDeprecatedDataReductionProxyViaValue)
+ if (value == kDeprecatedDataReductionProxyViaValue) {
+ if (has_intermediary)
+ *has_intermediary = !(headers->EnumerateHeader(&iter, "via", &value));
return true;
+ }
return false;
}
-net::ProxyService::DataReductionProxyBypassEventType
-GetDataReductionProxyBypassEventType(
+DataReductionProxyBypassType GetDataReductionProxyBypassType(
const net::HttpResponseHeaders* headers,
DataReductionProxyInfo* data_reduction_proxy_info) {
DCHECK(data_reduction_proxy_info);
- if (GetDataReductionProxyInfo(headers, data_reduction_proxy_info)) {
+ if (ParseHeadersAndSetProxyInfo(headers, data_reduction_proxy_info)) {
// A chrome-proxy response header is only present in a 502. For proper
// reporting, this check must come before the 5xx checks below.
- if (data_reduction_proxy_info->bypass_duration < TimeDelta::FromMinutes(30))
- return ProxyService::SHORT_BYPASS;
- return ProxyService::LONG_BYPASS;
- }
- if (headers->response_code() == net::HTTP_INTERNAL_SERVER_ERROR ||
- headers->response_code() == net::HTTP_BAD_GATEWAY ||
- headers->response_code() == net::HTTP_SERVICE_UNAVAILABLE) {
- // Fall back if a 500, 502 or 503 is returned.
- return ProxyService::INTERNAL_SERVER_ERROR_BYPASS;
+ if (!data_reduction_proxy_info->mark_proxies_as_bad)
+ return BYPASS_EVENT_TYPE_CURRENT;
+
+ const TimeDelta& duration = data_reduction_proxy_info->bypass_duration;
+ if (duration <= TimeDelta::FromSeconds(kShortBypassMaxSeconds))
+ return BYPASS_EVENT_TYPE_SHORT;
+ if (duration <= TimeDelta::FromSeconds(kMediumBypassMaxSeconds))
+ return BYPASS_EVENT_TYPE_MEDIUM;
+ return BYPASS_EVENT_TYPE_LONG;
}
+
+ // If a bypass is triggered by any of the following cases, then the data
+ // reduction proxy should be bypassed for a random duration between 1 and 5
+ // minutes.
+ data_reduction_proxy_info->mark_proxies_as_bad = true;
+ data_reduction_proxy_info->bypass_duration = GetDefaultBypassDuration();
+
+ // Fall back if a 500, 502 or 503 is returned.
+ if (headers->response_code() == net::HTTP_INTERNAL_SERVER_ERROR)
+ return BYPASS_EVENT_TYPE_STATUS_500_HTTP_INTERNAL_SERVER_ERROR;
+ if (headers->response_code() == net::HTTP_BAD_GATEWAY)
+ return BYPASS_EVENT_TYPE_STATUS_502_HTTP_BAD_GATEWAY;
+ if (headers->response_code() == net::HTTP_SERVICE_UNAVAILABLE)
+ return BYPASS_EVENT_TYPE_STATUS_503_HTTP_SERVICE_UNAVAILABLE;
// TODO(kundaji): Bypass if Proxy-Authenticate header value cannot be
// interpreted by data reduction proxy.
if (headers->response_code() == net::HTTP_PROXY_AUTHENTICATION_REQUIRED &&
!headers->HasHeader("Proxy-Authenticate")) {
- return ProxyService::MALFORMED_407_BYPASS;
+ return BYPASS_EVENT_TYPE_MALFORMED_407;
}
- if (!HasDataReductionProxyViaHeader(headers) &&
+ if (!HasDataReductionProxyViaHeader(headers, NULL) &&
(headers->response_code() != net::HTTP_NOT_MODIFIED)) {
// A Via header might not be present in a 304. Since the goal of a 304
// response is to minimize information transfer, a sender in general
// Separate this case from other responses that are missing the header.
if (headers->response_code() >= net::HTTP_BAD_REQUEST &&
headers->response_code() < net::HTTP_INTERNAL_SERVER_ERROR) {
- return ProxyService::PROXY_4XX_BYPASS;
+ return BYPASS_EVENT_TYPE_MISSING_VIA_HEADER_4XX;
}
- return ProxyService::MISSING_VIA_HEADER;
+ return BYPASS_EVENT_TYPE_MISSING_VIA_HEADER_OTHER;
}
// There is no bypass event.
- return ProxyService::BYPASS_EVENT_TYPE_MAX;
+ return BYPASS_EVENT_TYPE_MAX;
+}
+
+bool GetDataReductionProxyActionFingerprintChromeProxy(
+ const net::HttpResponseHeaders* headers,
+ std::string* chrome_proxy_fingerprint) {
+ return GetDataReductionProxyActionValue(
+ headers,
+ kChromeProxyActionFingerprintChromeProxy,
+ chrome_proxy_fingerprint);
+}
+
+bool GetDataReductionProxyActionFingerprintVia(
+ const net::HttpResponseHeaders* headers,
+ std::string* via_fingerprint) {
+ return GetDataReductionProxyActionValue(
+ headers,
+ kChromeProxyActionFingerprintVia,
+ via_fingerprint);
+}
+
+bool GetDataReductionProxyActionFingerprintOtherHeaders(
+ const net::HttpResponseHeaders* headers,
+ std::string* other_headers_fingerprint) {
+ return GetDataReductionProxyActionValue(
+ headers,
+ kChromeProxyActionFingerprintOtherHeaders,
+ other_headers_fingerprint);
+}
+
+bool GetDataReductionProxyActionFingerprintContentLength(
+ const net::HttpResponseHeaders* headers,
+ std::string* content_length_fingerprint) {
+ return GetDataReductionProxyActionValue(
+ headers,
+ kChromeProxyActionFingerprintContentLength,
+ content_length_fingerprint);
+}
+
+void GetDataReductionProxyHeaderWithFingerprintRemoved(
+ const net::HttpResponseHeaders* headers,
+ std::vector<std::string>* values) {
+ DCHECK(values);
+ std::string chrome_proxy_fingerprint_prefix = std::string(
+ kChromeProxyActionFingerprintChromeProxy) + kActionValueDelimiter;
+
+ std::string value;
+ void* iter = NULL;
+ while (headers->EnumerateHeader(&iter, kChromeProxyHeader, &value)) {
+ if (value.size() > chrome_proxy_fingerprint_prefix.size()) {
+ if (LowerCaseEqualsASCII(
+ value.begin(),
+ value.begin() + chrome_proxy_fingerprint_prefix.size(),
+ chrome_proxy_fingerprint_prefix.c_str())) {
+ continue;
+ }
+ }
+ values->push_back(value);
+ }
}
} // namespace data_reduction_proxy