blob: ea806682f9cbf223026b94d5be35c7db86adbc99 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_request_options.h"
#include <stdint.h>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/rand_util.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/safe_sprintf.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_split.h"
#include "base/strings/string_tokenizer.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_config.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_features.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_headers.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_switches.h"
#include "components/variations/variations_associated_data.h"
#include "crypto/random.h"
#include "net/base/host_port_pair.h"
#include "net/base/load_flags.h"
#include "net/base/proxy_server.h"
#include "net/url_request/url_request.h"
#if defined(USE_GOOGLE_API_KEYS_FOR_AUTH_KEY)
#include "google_apis/google_api_keys.h"
#endif
namespace data_reduction_proxy {
namespace {
std::string FormatOption(const std::string& name, const std::string& value) {
return name + "=" + value;
}
} // namespace
const char kSessionHeaderOption[] = "ps";
const char kCredentialsHeaderOption[] = "sid";
const char kSecureSessionHeaderOption[] = "s";
const char kBuildNumberHeaderOption[] = "b";
const char kPatchNumberHeaderOption[] = "p";
const char kClientHeaderOption[] = "c";
const char kExperimentsOption[] = "exp";
const char kPageIdOption[] = "pid";
// The empty version for the authentication protocol. Currently used by
// Android webview.
#if defined(OS_ANDROID)
const char kAndroidWebViewProtocolVersion[] = "";
#endif
// static
bool DataReductionProxyRequestOptions::IsKeySetOnCommandLine() {
const base::CommandLine& command_line =
*base::CommandLine::ForCurrentProcess();
return command_line.HasSwitch(
data_reduction_proxy::switches::kDataReductionProxyKey);
}
DataReductionProxyRequestOptions::DataReductionProxyRequestOptions(
Client client,
DataReductionProxyConfig* config)
: DataReductionProxyRequestOptions(client,
util::ChromiumVersion(),
config) {}
DataReductionProxyRequestOptions::DataReductionProxyRequestOptions(
Client client,
const std::string& version,
DataReductionProxyConfig* config)
: client_(util::GetStringForClient(client)),
use_assigned_credentials_(false),
data_reduction_proxy_config_(config),
current_page_id_(base::RandUint64()) {
DCHECK(data_reduction_proxy_config_);
util::GetChromiumBuildAndPatch(version, &build_, &patch_);
}
DataReductionProxyRequestOptions::~DataReductionProxyRequestOptions() {
}
void DataReductionProxyRequestOptions::Init() {
DCHECK(thread_checker_.CalledOnValidThread());
key_ = GetDefaultKey(),
UpdateCredentials();
UpdateExperiments();
// Called on the UI thread, but should be checked on the IO thread.
thread_checker_.DetachFromThread();
}
std::string DataReductionProxyRequestOptions::GetHeaderValueForTesting() const {
DCHECK(thread_checker_.CalledOnValidThread());
return header_value_;
}
void DataReductionProxyRequestOptions::UpdateExperiments() {
experiments_.clear();
std::string experiments =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
data_reduction_proxy::switches::kDataReductionProxyExperiment);
// The command line override takes precedence over field trial "exp"
// directives.
if (!experiments.empty()) {
base::StringTokenizer experiment_tokenizer(experiments, ", ");
experiment_tokenizer.set_quote_chars("\"");
while (experiment_tokenizer.GetNext()) {
if (!experiment_tokenizer.token().empty())
experiments_.push_back(experiment_tokenizer.token());
}
} else {
// If no other "exp" directive is forced by flags, add the field trial
// value.
AddServerExperimentFromFieldTrial();
}
RegenerateRequestHeaderValue();
}
void DataReductionProxyRequestOptions::AddServerExperimentFromFieldTrial() {
if (!params::IsIncludedInServerExperimentsFieldTrial())
return;
const std::string server_experiment = variations::GetVariationParamValue(
params::GetServerExperimentsFieldTrialName(), kExperimentsOption);
if (!server_experiment.empty())
experiments_.push_back(server_experiment);
}
// static
base::string16 DataReductionProxyRequestOptions::AuthHashForSalt(
int64_t salt,
const std::string& key) {
std::string salted_key =
base::StringPrintf("%lld%s%lld",
static_cast<long long>(salt),
key.c_str(),
static_cast<long long>(salt));
return base::UTF8ToUTF16(base::MD5String(salted_key));
}
base::Time DataReductionProxyRequestOptions::Now() const {
DCHECK(thread_checker_.CalledOnValidThread());
return base::Time::Now();
}
void DataReductionProxyRequestOptions::RandBytes(void* output,
size_t length) const {
DCHECK(thread_checker_.CalledOnValidThread());
crypto::RandBytes(output, length);
}
void DataReductionProxyRequestOptions::AddRequestHeader(
net::HttpRequestHeaders* request_headers,
base::Optional<uint64_t> page_id) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(!page_id || page_id.value() > 0u);
base::Time now = Now();
// Authorization credentials must be regenerated if they are expired.
if (!use_assigned_credentials_ && (now > credentials_expiration_time_))
UpdateCredentials();
const char kChromeProxyHeader[] = "Chrome-Proxy";
std::string header_value;
if (request_headers->HasHeader(kChromeProxyHeader)) {
request_headers->GetHeader(kChromeProxyHeader, &header_value);
request_headers->RemoveHeader(kChromeProxyHeader);
header_value += ", ";
}
header_value += header_value_;
if (page_id) {
// 64 bit uint fits in 16 characters when represented in hexadecimal, but
// there needs to be a trailing null termianted character in the buffer.
char page_id_buffer[17];
if (base::strings::SafeSPrintf(page_id_buffer, "%x", page_id.value()) > 0) {
header_value += ", " + FormatOption(kPageIdOption, page_id_buffer);
}
uint64_t page_id_tested;
DCHECK(base::HexStringToUInt64(page_id_buffer, &page_id_tested) &&
page_id_tested == page_id.value());
ALLOW_UNUSED_LOCAL(page_id_tested);
}
request_headers->SetHeader(kChromeProxyHeader, header_value);
}
void DataReductionProxyRequestOptions::ComputeCredentials(
const base::Time& now,
std::string* session,
std::string* credentials) const {
DCHECK(session);
DCHECK(credentials);
int64_t timestamp = (now - base::Time::UnixEpoch()).InMilliseconds() / 1000;
int32_t rand[3];
RandBytes(rand, 3 * sizeof(rand[0]));
*session = base::StringPrintf("%lld-%u-%u-%u",
static_cast<long long>(timestamp),
rand[0],
rand[1],
rand[2]);
*credentials = base::UTF16ToUTF8(AuthHashForSalt(timestamp, key_));
DVLOG(1) << "session: [" << *session << "] "
<< "password: [" << *credentials << "]";
}
void DataReductionProxyRequestOptions::UpdateCredentials() {
base::Time now = Now();
ComputeCredentials(now, &session_, &credentials_);
credentials_expiration_time_ = now + base::TimeDelta::FromHours(24);
RegenerateRequestHeaderValue();
}
void DataReductionProxyRequestOptions::SetKeyOnIO(const std::string& key) {
DCHECK(thread_checker_.CalledOnValidThread());
if(!key.empty()) {
key_ = key;
UpdateCredentials();
}
}
void DataReductionProxyRequestOptions::SetSecureSession(
const std::string& secure_session) {
DCHECK(thread_checker_.CalledOnValidThread());
session_.clear();
credentials_.clear();
secure_session_ = secure_session;
// Reset Page ID, so users can't be tracked across sessions.
ResetPageId();
// Force skipping of credential regeneration. It should be handled by the
// caller.
use_assigned_credentials_ = true;
RegenerateRequestHeaderValue();
}
void DataReductionProxyRequestOptions::Invalidate() {
DCHECK(thread_checker_.CalledOnValidThread());
SetSecureSession(std::string());
}
std::string DataReductionProxyRequestOptions::GetDefaultKey() const {
DCHECK(thread_checker_.CalledOnValidThread());
const base::CommandLine& command_line =
*base::CommandLine::ForCurrentProcess();
std::string key =
command_line.GetSwitchValueASCII(switches::kDataReductionProxyKey);
// Chrome on iOS gets the default key from a preprocessor constant. Chrome on
// Android and Chrome on desktop get the key from google_apis. Cronet and
// Webview have no default key.
#if defined(OS_IOS)
#if defined(SPDY_PROXY_AUTH_VALUE)
if (key.empty())
key = SPDY_PROXY_AUTH_VALUE;
#endif
#elif USE_GOOGLE_API_KEYS_FOR_AUTH_KEY
if (key.empty()) {
key = google_apis::GetSpdyProxyAuthValue();
}
#endif // defined(OS_IOS)
return key;
}
const std::string& DataReductionProxyRequestOptions::GetSecureSession() const {
return secure_session_;
}
void DataReductionProxyRequestOptions::RegenerateRequestHeaderValue() {
std::vector<std::string> headers;
if (!session_.empty())
headers.push_back(FormatOption(kSessionHeaderOption, session_));
if (!credentials_.empty())
headers.push_back(FormatOption(kCredentialsHeaderOption, credentials_));
if (!secure_session_.empty()) {
headers.push_back(
FormatOption(kSecureSessionHeaderOption, secure_session_));
}
if (!client_.empty())
headers.push_back(FormatOption(kClientHeaderOption, client_));
DCHECK(!build_.empty());
headers.push_back(FormatOption(kBuildNumberHeaderOption, build_));
DCHECK(!patch_.empty());
headers.push_back(FormatOption(kPatchNumberHeaderOption, patch_));
for (const auto& experiment : experiments_)
headers.push_back(FormatOption(kExperimentsOption, experiment));
header_value_ = base::JoinString(headers, ", ");
if (update_header_callback_) {
net::HttpRequestHeaders headers;
headers.SetHeader(chrome_proxy_header(), header_value_);
update_header_callback_.Run(std::move(headers));
}
}
std::string DataReductionProxyRequestOptions::GetSessionKeyFromRequestHeaders(
const net::HttpRequestHeaders& request_headers) const {
std::string chrome_proxy_header_value;
base::StringPairs kv_pairs;
// Return if the request does not have request headers or if they can't be
// parsed into key-value pairs.
if (!request_headers.GetHeader(chrome_proxy_header(),
&chrome_proxy_header_value) ||
!base::SplitStringIntoKeyValuePairs(chrome_proxy_header_value,
'=', // Key-value delimiter
',', // Key-value pair delimiter
&kv_pairs)) {
return "";
}
for (const auto& kv_pair : kv_pairs) {
// Delete leading and trailing white space characters from the key before
// comparing.
if (base::TrimWhitespaceASCII(kv_pair.first, base::TRIM_ALL) ==
kSecureSessionHeaderOption) {
return base::TrimWhitespaceASCII(kv_pair.second, base::TRIM_ALL)
.as_string();
}
}
return "";
}
uint64_t DataReductionProxyRequestOptions::GeneratePageId() {
// Caller should not depend on order.
return ++current_page_id_;
}
void DataReductionProxyRequestOptions::ResetPageId() {
current_page_id_ = base::RandUint64();
}
} // namespace data_reduction_proxy