| Index: net/base/sdch_manager.cc
|
| diff --git a/net/base/sdch_manager.cc b/net/base/sdch_manager.cc
|
| index fee75e58f2fd11170213447a1fc911547e261ab4..42b5cd19e4fc655aa7226d08b649796961a632fe 100644
|
| --- a/net/base/sdch_manager.cc
|
| +++ b/net/base/sdch_manager.cc
|
| @@ -9,12 +9,22 @@
|
| #include "base/metrics/histogram.h"
|
| #include "base/strings/string_number_conversions.h"
|
| #include "base/strings/string_util.h"
|
| +#include "base/values.h"
|
| #include "crypto/sha2.h"
|
| #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
|
| #include "net/url_request/url_request_http_job.h"
|
|
|
| namespace net {
|
|
|
| +namespace {
|
| +
|
| +bool IsDictionaryProblem(SdchManager::ProblemCodes code) {
|
| + return code >= SdchManager::DICTIONARY_HAS_NO_HEADER &&
|
| + code <= SdchManager::DICTIONARY_ALREADY_TRIED_TO_DOWNLOAD;
|
| +}
|
| +
|
| +}
|
| +
|
| //------------------------------------------------------------------------------
|
| // static
|
|
|
| @@ -87,6 +97,24 @@ bool SdchManager::Dictionary::CanAdvertise(const GURL& target_url) {
|
| return true;
|
| }
|
|
|
| +base::DictionaryValue*
|
| +SdchManager::Dictionary::DictionaryInfoToValue() const {
|
| + base::DictionaryValue* value = new base::DictionaryValue();
|
| + value->SetString("url", url_.spec());
|
| + value->SetString("client_hash", client_hash_);
|
| + value->SetString("domain", domain_);
|
| + value->SetString("path", path_);
|
| + value->SetString("expiration",
|
| + base::Int64ToString(expiration_.ToInternalValue()));
|
| + base::ListValue* port_list = new base::ListValue();
|
| + for (std::set<int>::const_iterator it = ports_.begin();
|
| + it != ports_.end(); ++it) {
|
| + port_list->AppendInteger(*it);
|
| + }
|
| + value->Set("ports", port_list);
|
| + return value;
|
| +}
|
| +
|
| //------------------------------------------------------------------------------
|
| // Security functions restricting loads and use of dictionaries.
|
|
|
| @@ -94,7 +122,8 @@ bool SdchManager::Dictionary::CanAdvertise(const GURL& target_url) {
|
| bool SdchManager::Dictionary::CanSet(const std::string& domain,
|
| const std::string& path,
|
| const std::set<int>& ports,
|
| - const GURL& dictionary_url) {
|
| + const GURL& dictionary_url,
|
| + SdchManager* sdch_manager) {
|
| /*
|
| A dictionary is invalid and must not be stored if any of the following are
|
| true:
|
| @@ -114,17 +143,20 @@ bool SdchManager::Dictionary::CanSet(const std::string& domain,
|
| // were any... then don't allow the dictionary to be set).
|
|
|
| if (domain.empty()) {
|
| - SdchErrorRecovery(DICTIONARY_MISSING_DOMAIN_SPECIFIER);
|
| + sdch_manager->SdchErrorRecovery(
|
| + DICTIONARY_MISSING_DOMAIN_SPECIFIER, dictionary_url);
|
| return false; // Domain is required.
|
| }
|
| if (registry_controlled_domains::GetDomainAndRegistry(
|
| domain,
|
| registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES).empty()) {
|
| - SdchErrorRecovery(DICTIONARY_SPECIFIES_TOP_LEVEL_DOMAIN);
|
| + sdch_manager->SdchErrorRecovery(
|
| + DICTIONARY_SPECIFIES_TOP_LEVEL_DOMAIN, dictionary_url);
|
| return false; // domain was a TLD.
|
| }
|
| if (!Dictionary::DomainMatch(dictionary_url, domain)) {
|
| - SdchErrorRecovery(DICTIONARY_DOMAIN_NOT_MATCHING_SOURCE_URL);
|
| + sdch_manager->SdchErrorRecovery(
|
| + DICTIONARY_DOMAIN_NOT_MATCHING_SOURCE_URL, dictionary_url);
|
| return false;
|
| }
|
|
|
| @@ -136,21 +168,23 @@ bool SdchManager::Dictionary::CanSet(const std::string& domain,
|
| size_t end_of_host_index = referrer_url_host.find_first_of('.');
|
| if (referrer_url_host.npos != end_of_host_index &&
|
| end_of_host_index < postfix_domain_index) {
|
| - SdchErrorRecovery(DICTIONARY_REFERER_URL_HAS_DOT_IN_PREFIX);
|
| + sdch_manager->SdchErrorRecovery(
|
| + DICTIONARY_REFERER_URL_HAS_DOT_IN_PREFIX, dictionary_url);
|
| return false;
|
| }
|
| }
|
|
|
| if (!ports.empty()
|
| && 0 == ports.count(dictionary_url.EffectiveIntPort())) {
|
| - SdchErrorRecovery(DICTIONARY_PORT_NOT_MATCHING_SOURCE_URL);
|
| + sdch_manager->SdchErrorRecovery(
|
| + DICTIONARY_PORT_NOT_MATCHING_SOURCE_URL, dictionary_url);
|
| return false;
|
| }
|
| return true;
|
| }
|
|
|
| -// static
|
| -bool SdchManager::Dictionary::CanUse(const GURL& referring_url) {
|
| +bool SdchManager::Dictionary::CanUse(
|
| + const GURL& referring_url, SdchManager* sdch_manager) {
|
| /*
|
| 1. The request URL's host name domain-matches the Domain attribute of the
|
| dictionary.
|
| @@ -163,32 +197,38 @@ bool SdchManager::Dictionary::CanUse(const GURL& referring_url) {
|
| url scheme.
|
| */
|
| if (!DomainMatch(referring_url, domain_)) {
|
| - SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_DOMAIN);
|
| + sdch_manager->SdchErrorRecovery(
|
| + DICTIONARY_FOUND_HAS_WRONG_DOMAIN, referring_url);
|
| return false;
|
| }
|
| if (!ports_.empty()
|
| && 0 == ports_.count(referring_url.EffectiveIntPort())) {
|
| - SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_PORT_LIST);
|
| + sdch_manager->SdchErrorRecovery(
|
| + DICTIONARY_FOUND_HAS_WRONG_PORT_LIST, referring_url);
|
| return false;
|
| }
|
| if (path_.size() && !PathMatch(referring_url.path(), path_)) {
|
| - SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_PATH);
|
| + sdch_manager->SdchErrorRecovery(
|
| + DICTIONARY_FOUND_HAS_WRONG_PATH, referring_url);
|
| return false;
|
| }
|
| if (!SdchManager::secure_scheme_supported() &&
|
| referring_url.SchemeIsSecure()) {
|
| - SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_SCHEME);
|
| + sdch_manager->SdchErrorRecovery(
|
| + DICTIONARY_FOUND_HAS_WRONG_SCHEME, referring_url);
|
| return false;
|
| }
|
| if (referring_url.SchemeIsSecure() != url_.SchemeIsSecure()) {
|
| - SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_SCHEME);
|
| + sdch_manager->SdchErrorRecovery(
|
| + DICTIONARY_FOUND_HAS_WRONG_SCHEME, referring_url);
|
| return false;
|
| }
|
|
|
| // TODO(jar): Remove overly restrictive failsafe test (added per security
|
| // review) when we have a need to be more general.
|
| if (!referring_url.SchemeIsHTTPOrHTTPS()) {
|
| - SdchErrorRecovery(ATTEMPT_TO_DECODE_NON_HTTP_DATA);
|
| + sdch_manager->SdchErrorRecovery(
|
| + ATTEMPT_TO_DECODE_NON_HTTP_DATA, referring_url);
|
| return false;
|
| }
|
|
|
| @@ -247,8 +287,10 @@ void SdchManager::ClearData() {
|
| }
|
|
|
| // static
|
| -void SdchManager::SdchErrorRecovery(ProblemCodes problem) {
|
| +void SdchManager::SdchErrorRecovery(
|
| + ProblemCodes problem, const GURL& url) const {
|
| UMA_HISTOGRAM_ENUMERATION("Sdch3.ProblemCodes_4", problem, MAX_PROBLEM_CODE);
|
| + problems_.push_back(std::make_pair(url.spec(), problem));
|
| }
|
|
|
| void SdchManager::set_sdch_fetcher(SdchFetcher* fetcher) {
|
| @@ -334,7 +376,7 @@ bool SdchManager::IsInSupportedDomain(const GURL& url) {
|
| blacklisted_domains_[domain] = count;
|
| else
|
| blacklisted_domains_.erase(domain);
|
| - SdchErrorRecovery(DOMAIN_BLACKLIST_INCLUDES_TARGET);
|
| + SdchErrorRecovery(DOMAIN_BLACKLIST_INCLUDES_TARGET, url);
|
| return false;
|
| }
|
|
|
| @@ -362,18 +404,19 @@ bool SdchManager::CanFetchDictionary(const GURL& referring_url,
|
| // I take "host name match" to be "is identical to"
|
| if (referring_url.host() != dictionary_url.host() ||
|
| referring_url.scheme() != dictionary_url.scheme()) {
|
| - SdchErrorRecovery(DICTIONARY_LOAD_ATTEMPT_FROM_DIFFERENT_HOST);
|
| + SdchErrorRecovery(
|
| + DICTIONARY_LOAD_ATTEMPT_FROM_DIFFERENT_HOST, dictionary_url);
|
| return false;
|
| }
|
| if (!secure_scheme_supported() && referring_url.SchemeIsSecure()) {
|
| - SdchErrorRecovery(DICTIONARY_SELECTED_FOR_SSL);
|
| + SdchErrorRecovery(DICTIONARY_SELECTED_FOR_SSL, dictionary_url);
|
| return false;
|
| }
|
|
|
| // TODO(jar): Remove this failsafe conservative hack which is more restrictive
|
| // than current SDCH spec when needed, and justified by security audit.
|
| if (!referring_url.SchemeIsHTTPOrHTTPS()) {
|
| - SdchErrorRecovery(DICTIONARY_SELECTED_FROM_NON_HTTP);
|
| + SdchErrorRecovery(DICTIONARY_SELECTED_FROM_NON_HTTP, dictionary_url);
|
| return false;
|
| }
|
|
|
| @@ -387,7 +430,7 @@ bool SdchManager::AddSdchDictionary(const std::string& dictionary_text,
|
| std::string server_hash;
|
| GenerateHash(dictionary_text, &client_hash, &server_hash);
|
| if (dictionaries_.find(server_hash) != dictionaries_.end()) {
|
| - SdchErrorRecovery(DICTIONARY_ALREADY_LOADED);
|
| + SdchErrorRecovery(DICTIONARY_ALREADY_LOADED, dictionary_url);
|
| return false; // Already loaded.
|
| }
|
|
|
| @@ -396,13 +439,13 @@ bool SdchManager::AddSdchDictionary(const std::string& dictionary_text,
|
| base::Time expiration(base::Time::Now() + base::TimeDelta::FromDays(30));
|
|
|
| if (dictionary_text.empty()) {
|
| - SdchErrorRecovery(DICTIONARY_HAS_NO_TEXT);
|
| + SdchErrorRecovery(DICTIONARY_HAS_NO_TEXT, dictionary_url);
|
| return false; // Missing header.
|
| }
|
|
|
| size_t header_end = dictionary_text.find("\n\n");
|
| if (std::string::npos == header_end) {
|
| - SdchErrorRecovery(DICTIONARY_HAS_NO_HEADER);
|
| + SdchErrorRecovery(DICTIONARY_HAS_NO_HEADER, dictionary_url);
|
| return false; // Missing header.
|
| }
|
| size_t line_start = 0; // Start of line being parsed.
|
| @@ -413,7 +456,8 @@ bool SdchManager::AddSdchDictionary(const std::string& dictionary_text,
|
|
|
| size_t colon_index = dictionary_text.find(':', line_start);
|
| if (std::string::npos == colon_index) {
|
| - SdchErrorRecovery(DICTIONARY_HEADER_LINE_MISSING_COLON);
|
| + SdchErrorRecovery(
|
| + DICTIONARY_HEADER_LINE_MISSING_COLON, dictionary_url);
|
| return false; // Illegal line missing a colon.
|
| }
|
|
|
| @@ -455,7 +499,7 @@ bool SdchManager::AddSdchDictionary(const std::string& dictionary_text,
|
| if (!IsInSupportedDomain(dictionary_url))
|
| return false;
|
|
|
| - if (!Dictionary::CanSet(domain, path, ports, dictionary_url))
|
| + if (!Dictionary::CanSet(domain, path, ports, dictionary_url, this))
|
| return false;
|
|
|
| // TODO(jar): Remove these hacks to preclude a DOS attack involving piles of
|
| @@ -463,11 +507,11 @@ bool SdchManager::AddSdchDictionary(const std::string& dictionary_text,
|
| // instead of just blocking additions. For now, with the spec in flux, it
|
| // is probably not worth doing eviction handling.
|
| if (kMaxDictionarySize < dictionary_text.size()) {
|
| - SdchErrorRecovery(DICTIONARY_IS_TOO_LARGE);
|
| + SdchErrorRecovery(DICTIONARY_IS_TOO_LARGE, dictionary_url);
|
| return false;
|
| }
|
| if (kMaxDictionaryCount <= dictionaries_.size()) {
|
| - SdchErrorRecovery(DICTIONARY_COUNT_EXCEEDED);
|
| + SdchErrorRecovery(DICTIONARY_COUNT_EXCEEDED, dictionary_url);
|
| return false;
|
| }
|
|
|
| @@ -494,7 +538,7 @@ void SdchManager::GetVcdiffDictionary(
|
| scoped_refptr<Dictionary> matching_dictionary = it->second;
|
| if (!IsInSupportedDomain(referring_url))
|
| return;
|
| - if (!matching_dictionary->CanUse(referring_url))
|
| + if (!matching_dictionary->CanUse(referring_url, this))
|
| return;
|
| *dictionary = matching_dictionary;
|
| }
|
| @@ -537,6 +581,51 @@ void SdchManager::GenerateHash(const std::string& dictionary_text,
|
| DCHECK_EQ(client_hash->length(), 8u);
|
| }
|
|
|
| +base::Value* SdchManager::SdchInfoToValue() const {
|
| + base::DictionaryValue* value = new base::DictionaryValue();
|
| +
|
| + value->SetBoolean("sdch_enabled", sdch_enabled());
|
| + value->SetBoolean("secure_scheme_support", secure_scheme_supported());
|
| +
|
| + base::ListValue* entry_list = new base::ListValue();
|
| + for (DictionaryMap::const_iterator it = dictionaries_.begin();
|
| + it != dictionaries_.end(); ++it) {
|
| + base::DictionaryValue* entry_dict = it->second->DictionaryInfoToValue();
|
| + entry_dict->SetString("server_hash", it->first);
|
| + entry_list->Append(entry_dict);
|
| + }
|
| + value->Set("dictionaries", entry_list);
|
| +
|
| + entry_list = new base::ListValue();
|
| + for (DomainCounter::const_iterator it = blacklisted_domains_.begin();
|
| + it != blacklisted_domains_.end(); ++it) {
|
| + base::DictionaryValue* entry_dict = new base::DictionaryValue();
|
| + entry_dict->SetString("domain", it->first);
|
| + if (it->second != INT_MAX)
|
| + entry_dict->SetInteger("tries", it->second);
|
| + entry_list->Append(entry_dict);
|
| + }
|
| + value->Set("blacklisted", entry_list);
|
| +
|
| +
|
| + base::ListValue* dict_problems_list = new base::ListValue();
|
| + base::ListValue* usage_problems_list = new base::ListValue();
|
| + for (size_t i = 0; i < problems_.size(); ++i) {
|
| + base::DictionaryValue* entry = new base::DictionaryValue();
|
| + entry->SetString("url", problems_[i].first);
|
| + entry->SetInteger("error", problems_[i].second);
|
| + if (IsDictionaryProblem(problems_[i].second)) {
|
| + dict_problems_list->Append(entry);
|
| + } else {
|
| + usage_problems_list->Append(entry);
|
| + }
|
| + }
|
| + value->Set("dict_errors", dict_problems_list);
|
| + value->Set("usage_errors", usage_problems_list);
|
| +
|
| + return value;
|
| +}
|
| +
|
| //------------------------------------------------------------------------------
|
| // Methods for supporting latency experiments.
|
|
|
| @@ -555,7 +644,7 @@ void SdchManager::SetAllowLatencyExperiment(const GURL& url, bool enable) {
|
| ExperimentSet::iterator it = allow_latency_experiment_.find(url.host());
|
| if (allow_latency_experiment_.end() == it)
|
| return; // It was already erased, or never allowed.
|
| - SdchErrorRecovery(LATENCY_TEST_DISALLOWED);
|
| + SdchErrorRecovery(LATENCY_TEST_DISALLOWED, url);
|
| allow_latency_experiment_.erase(it);
|
| }
|
|
|
|
|