This source file includes following definitions.
- ports_
- CanAdvertise
- CanSet
- CanUse
- PathMatch
- DomainMatch
- Shutdown
- Global
- SdchErrorRecovery
- set_sdch_fetcher
- EnableSdchSupport
- EnableSecureSchemeSupport
- BlacklistDomain
- BlacklistDomainForever
- ClearBlacklistings
- ClearDomainBlacklisting
- BlackListDomainCount
- BlacklistDomainExponential
- IsInSupportedDomain
- FetchDictionary
- CanFetchDictionary
- AddSdchDictionary
- GetVcdiffDictionary
- GetAvailDictionaryList
- GenerateHash
- AllowLatencyExperiment
- SetAllowLatencyExperiment
- UrlSafeBase64Encode
#include "net/base/sdch_manager.h"
#include "base/base64.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.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 {
const size_t SdchManager::kMaxDictionarySize = 1000000;
const size_t SdchManager::kMaxDictionaryCount = 20;
SdchManager* SdchManager::global_ = NULL;
bool SdchManager::g_sdch_enabled_ = true;
bool SdchManager::g_secure_scheme_supported_ = false;
SdchManager::Dictionary::Dictionary(const std::string& dictionary_text,
size_t offset,
const std::string& client_hash,
const GURL& gurl,
const std::string& domain,
const std::string& path,
const base::Time& expiration,
const std::set<int>& ports)
: text_(dictionary_text, offset),
client_hash_(client_hash),
url_(gurl),
domain_(domain),
path_(path),
expiration_(expiration),
ports_(ports) {
}
SdchManager::Dictionary::~Dictionary() {
}
bool SdchManager::Dictionary::CanAdvertise(const GURL& target_url) {
if (!SdchManager::Global()->IsInSupportedDomain(target_url))
return false;
if (!DomainMatch(target_url, domain_))
return false;
if (!ports_.empty() && 0 == ports_.count(target_url.EffectiveIntPort()))
return false;
if (path_.size() && !PathMatch(target_url.path(), path_))
return false;
if (!SdchManager::secure_scheme_supported() && target_url.SchemeIsSecure())
return false;
if (target_url.SchemeIsSecure() && !url_.SchemeIsSecure())
return false;
if (base::Time::Now() > expiration_)
return false;
return true;
}
bool SdchManager::Dictionary::CanSet(const std::string& domain,
const std::string& path,
const std::set<int>& ports,
const GURL& dictionary_url) {
if (!SdchManager::Global()->IsInSupportedDomain(dictionary_url))
return false;
if (domain.empty()) {
SdchErrorRecovery(DICTIONARY_MISSING_DOMAIN_SPECIFIER);
return false;
}
if (registry_controlled_domains::GetDomainAndRegistry(
domain,
registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES).empty()) {
SdchErrorRecovery(DICTIONARY_SPECIFIES_TOP_LEVEL_DOMAIN);
return false;
}
if (!Dictionary::DomainMatch(dictionary_url, domain)) {
SdchErrorRecovery(DICTIONARY_DOMAIN_NOT_MATCHING_SOURCE_URL);
return false;
}
std::string referrer_url_host = dictionary_url.host();
size_t postfix_domain_index = referrer_url_host.rfind(domain);
if (referrer_url_host.size() == postfix_domain_index + domain.size()) {
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);
return false;
}
}
if (!ports.empty()
&& 0 == ports.count(dictionary_url.EffectiveIntPort())) {
SdchErrorRecovery(DICTIONARY_PORT_NOT_MATCHING_SOURCE_URL);
return false;
}
return true;
}
bool SdchManager::Dictionary::CanUse(const GURL& referring_url) {
if (!SdchManager::Global()->IsInSupportedDomain(referring_url))
return false;
if (!DomainMatch(referring_url, domain_)) {
SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_DOMAIN);
return false;
}
if (!ports_.empty()
&& 0 == ports_.count(referring_url.EffectiveIntPort())) {
SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_PORT_LIST);
return false;
}
if (path_.size() && !PathMatch(referring_url.path(), path_)) {
SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_PATH);
return false;
}
if (!SdchManager::secure_scheme_supported() &&
referring_url.SchemeIsSecure()) {
SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_SCHEME);
return false;
}
if (referring_url.SchemeIsSecure() && !url_.SchemeIsSecure()) {
SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_SCHEME);
return false;
}
if (!referring_url.SchemeIsHTTPOrHTTPS()) {
SdchErrorRecovery(ATTEMPT_TO_DECODE_NON_HTTP_DATA);
return false;
}
return true;
}
bool SdchManager::Dictionary::PathMatch(const std::string& path,
const std::string& restriction) {
if (path == restriction)
return true;
size_t prefix_length = restriction.size();
if (prefix_length > path.size())
return false;
if (0 != path.compare(0, prefix_length, restriction))
return false;
return restriction[prefix_length - 1] == '/' || path[prefix_length] == '/';
}
bool SdchManager::Dictionary::DomainMatch(const GURL& gurl,
const std::string& restriction) {
return gurl.DomainIs(restriction.data(), restriction.size());
}
SdchManager::SdchManager() {
DCHECK(!global_);
DCHECK(CalledOnValidThread());
global_ = this;
}
SdchManager::~SdchManager() {
DCHECK_EQ(this, global_);
DCHECK(CalledOnValidThread());
while (!dictionaries_.empty()) {
DictionaryMap::iterator it = dictionaries_.begin();
it->second->Release();
dictionaries_.erase(it->first);
}
global_ = NULL;
}
void SdchManager::Shutdown() {
EnableSdchSupport(false);
if (!global_ )
return;
global_->set_sdch_fetcher(NULL);
}
SdchManager* SdchManager::Global() {
return global_;
}
void SdchManager::SdchErrorRecovery(ProblemCodes problem) {
UMA_HISTOGRAM_ENUMERATION("Sdch3.ProblemCodes_4", problem, MAX_PROBLEM_CODE);
}
void SdchManager::set_sdch_fetcher(SdchFetcher* fetcher) {
DCHECK(CalledOnValidThread());
fetcher_.reset(fetcher);
}
void SdchManager::EnableSdchSupport(bool enabled) {
g_sdch_enabled_ = enabled;
}
void SdchManager::EnableSecureSchemeSupport(bool enabled) {
g_secure_scheme_supported_ = enabled;
}
void SdchManager::BlacklistDomain(const GURL& url) {
if (!global_ )
return;
global_->SetAllowLatencyExperiment(url, false);
std::string domain(StringToLowerASCII(url.host()));
int count = global_->blacklisted_domains_[domain];
if (count > 0)
return;
count = 1 + 2 * global_->exponential_blacklist_count[domain];
if (count > 0)
global_->exponential_blacklist_count[domain] = count;
else
count = INT_MAX;
global_->blacklisted_domains_[domain] = count;
}
void SdchManager::BlacklistDomainForever(const GURL& url) {
if (!global_ )
return;
global_->SetAllowLatencyExperiment(url, false);
std::string domain(StringToLowerASCII(url.host()));
global_->exponential_blacklist_count[domain] = INT_MAX;
global_->blacklisted_domains_[domain] = INT_MAX;
}
void SdchManager::ClearBlacklistings() {
Global()->blacklisted_domains_.clear();
Global()->exponential_blacklist_count.clear();
}
void SdchManager::ClearDomainBlacklisting(const std::string& domain) {
Global()->blacklisted_domains_.erase(StringToLowerASCII(domain));
}
int SdchManager::BlackListDomainCount(const std::string& domain) {
if (Global()->blacklisted_domains_.end() ==
Global()->blacklisted_domains_.find(domain))
return 0;
return Global()->blacklisted_domains_[StringToLowerASCII(domain)];
}
int SdchManager::BlacklistDomainExponential(const std::string& domain) {
if (Global()->exponential_blacklist_count.end() ==
Global()->exponential_blacklist_count.find(domain))
return 0;
return Global()->exponential_blacklist_count[StringToLowerASCII(domain)];
}
bool SdchManager::IsInSupportedDomain(const GURL& url) {
DCHECK(CalledOnValidThread());
if (!g_sdch_enabled_ )
return false;
if (blacklisted_domains_.empty())
return true;
std::string domain(StringToLowerASCII(url.host()));
DomainCounter::iterator it = blacklisted_domains_.find(domain);
if (blacklisted_domains_.end() == it)
return true;
int count = it->second - 1;
if (count > 0)
blacklisted_domains_[domain] = count;
else
blacklisted_domains_.erase(domain);
SdchErrorRecovery(DOMAIN_BLACKLIST_INCLUDES_TARGET);
return false;
}
void SdchManager::FetchDictionary(const GURL& request_url,
const GURL& dictionary_url) {
DCHECK(CalledOnValidThread());
if (SdchManager::Global()->CanFetchDictionary(request_url, dictionary_url) &&
fetcher_.get())
fetcher_->Schedule(dictionary_url);
}
bool SdchManager::CanFetchDictionary(const GURL& referring_url,
const GURL& dictionary_url) const {
DCHECK(CalledOnValidThread());
if (referring_url.host() != dictionary_url.host() ||
referring_url.scheme() != dictionary_url.scheme()) {
SdchErrorRecovery(DICTIONARY_LOAD_ATTEMPT_FROM_DIFFERENT_HOST);
return false;
}
if (!secure_scheme_supported() && referring_url.SchemeIsSecure()) {
SdchErrorRecovery(DICTIONARY_SELECTED_FOR_SSL);
return false;
}
if (!referring_url.SchemeIsHTTPOrHTTPS()) {
SdchErrorRecovery(DICTIONARY_SELECTED_FROM_NON_HTTP);
return false;
}
return true;
}
bool SdchManager::AddSdchDictionary(const std::string& dictionary_text,
const GURL& dictionary_url) {
DCHECK(CalledOnValidThread());
std::string client_hash;
std::string server_hash;
GenerateHash(dictionary_text, &client_hash, &server_hash);
if (dictionaries_.find(server_hash) != dictionaries_.end()) {
SdchErrorRecovery(DICTIONARY_ALREADY_LOADED);
return false;
}
std::string domain, path;
std::set<int> ports;
base::Time expiration(base::Time::Now() + base::TimeDelta::FromDays(30));
if (dictionary_text.empty()) {
SdchErrorRecovery(DICTIONARY_HAS_NO_TEXT);
return false;
}
size_t header_end = dictionary_text.find("\n\n");
if (std::string::npos == header_end) {
SdchErrorRecovery(DICTIONARY_HAS_NO_HEADER);
return false;
}
size_t line_start = 0;
while (1) {
size_t line_end = dictionary_text.find('\n', line_start);
DCHECK(std::string::npos != line_end);
DCHECK_LE(line_end, header_end);
size_t colon_index = dictionary_text.find(':', line_start);
if (std::string::npos == colon_index) {
SdchErrorRecovery(DICTIONARY_HEADER_LINE_MISSING_COLON);
return false;
}
if (colon_index > line_end)
break;
size_t value_start = dictionary_text.find_first_not_of(" \t",
colon_index + 1);
if (std::string::npos != value_start) {
if (value_start >= line_end)
break;
std::string name(dictionary_text, line_start, colon_index - line_start);
std::string value(dictionary_text, value_start, line_end - value_start);
name = StringToLowerASCII(name);
if (name == "domain") {
domain = value;
} else if (name == "path") {
path = value;
} else if (name == "format-version") {
if (value != "1.0")
return false;
} else if (name == "max-age") {
int64 seconds;
base::StringToInt64(value, &seconds);
expiration = base::Time::Now() + base::TimeDelta::FromSeconds(seconds);
} else if (name == "port") {
int port;
base::StringToInt(value, &port);
if (port >= 0)
ports.insert(port);
}
}
if (line_end >= header_end)
break;
line_start = line_end + 1;
}
if (!Dictionary::CanSet(domain, path, ports, dictionary_url))
return false;
if (kMaxDictionarySize < dictionary_text.size()) {
SdchErrorRecovery(DICTIONARY_IS_TOO_LARGE);
return false;
}
if (kMaxDictionaryCount <= dictionaries_.size()) {
SdchErrorRecovery(DICTIONARY_COUNT_EXCEEDED);
return false;
}
UMA_HISTOGRAM_COUNTS("Sdch3.Dictionary size loaded", dictionary_text.size());
DVLOG(1) << "Loaded dictionary with client hash " << client_hash
<< " and server hash " << server_hash;
Dictionary* dictionary =
new Dictionary(dictionary_text, header_end + 2, client_hash,
dictionary_url, domain, path, expiration, ports);
dictionary->AddRef();
dictionaries_[server_hash] = dictionary;
return true;
}
void SdchManager::GetVcdiffDictionary(const std::string& server_hash,
const GURL& referring_url, Dictionary** dictionary) {
DCHECK(CalledOnValidThread());
*dictionary = NULL;
DictionaryMap::iterator it = dictionaries_.find(server_hash);
if (it == dictionaries_.end()) {
return;
}
Dictionary* matching_dictionary = it->second;
if (!matching_dictionary->CanUse(referring_url))
return;
*dictionary = matching_dictionary;
}
void SdchManager::GetAvailDictionaryList(const GURL& target_url,
std::string* list) {
DCHECK(CalledOnValidThread());
int count = 0;
for (DictionaryMap::iterator it = dictionaries_.begin();
it != dictionaries_.end(); ++it) {
if (!it->second->CanAdvertise(target_url))
continue;
++count;
if (!list->empty())
list->append(",");
list->append(it->second->client_hash());
}
if (count > 0)
UMA_HISTOGRAM_COUNTS("Sdch3.Advertisement_Count", count);
}
void SdchManager::GenerateHash(const std::string& dictionary_text,
std::string* client_hash, std::string* server_hash) {
char binary_hash[32];
crypto::SHA256HashString(dictionary_text, binary_hash, sizeof(binary_hash));
std::string first_48_bits(&binary_hash[0], 6);
std::string second_48_bits(&binary_hash[6], 6);
UrlSafeBase64Encode(first_48_bits, client_hash);
UrlSafeBase64Encode(second_48_bits, server_hash);
DCHECK_EQ(server_hash->length(), 8u);
DCHECK_EQ(client_hash->length(), 8u);
}
bool SdchManager::AllowLatencyExperiment(const GURL& url) const {
DCHECK(CalledOnValidThread());
return allow_latency_experiment_.end() !=
allow_latency_experiment_.find(url.host());
}
void SdchManager::SetAllowLatencyExperiment(const GURL& url, bool enable) {
DCHECK(CalledOnValidThread());
if (enable) {
allow_latency_experiment_.insert(url.host());
return;
}
ExperimentSet::iterator it = allow_latency_experiment_.find(url.host());
if (allow_latency_experiment_.end() == it)
return;
SdchErrorRecovery(LATENCY_TEST_DISALLOWED);
allow_latency_experiment_.erase(it);
}
void SdchManager::UrlSafeBase64Encode(const std::string& input,
std::string* output) {
base::Base64Encode(input, output);
for (size_t i = 0; i < output->size(); ++i) {
switch (output->data()[i]) {
case '+':
(*output)[i] = '-';
continue;
case '/':
(*output)[i] = '_';
continue;
default:
continue;
}
}
}
}