| // 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 |