diff options
Diffstat (limited to 'net/base/sdch_manager.cc')
-rw-r--r-- | net/base/sdch_manager.cc | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/net/base/sdch_manager.cc b/net/base/sdch_manager.cc new file mode 100644 index 0000000..6c8bce3 --- /dev/null +++ b/net/base/sdch_manager.cc @@ -0,0 +1,336 @@ +// Copyright (c) 2006-2008 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 "base/histogram.h" +#include "base/logging.h" +#include "base/sha2.h" +#include "base/string_util.h" +#include "net/base/base64.h" +#include "net/base/registry_controlled_domain.h" +#include "net/base/sdch_manager.h" +#include "net/url_request/url_request_http_job.h" + + +//------------------------------------------------------------------------------ +// static +SdchManager* SdchManager::global_; + +// static +SdchManager* SdchManager::Global() { + return global_; +} + +//------------------------------------------------------------------------------ +SdchManager::SdchManager() : sdch_enabled_(false) { + DCHECK(!global_); + global_ = this; +} + +SdchManager::~SdchManager() { + DCHECK(global_ == this); + while (!dictionaries_.empty()) { + DictionaryMap::iterator it = dictionaries_.begin(); + it->second->Release(); + dictionaries_.erase(it->first); + } + global_ = NULL; +} + +const bool SdchManager::IsInSupportedDomain(const GURL& url) const { + return sdch_enabled_ && + (supported_domain_.empty() || + url.DomainIs(supported_domain_.data(), supported_domain_.size())); +} + +void SdchManager::FetchDictionary(const GURL& referring_url, + const GURL& dictionary_url) { + /* The user agent may retrieve a dictionary from the dictionary URL if all of + the following are true: + 1 The dictionary URL host name matches the referrer URL host name + 2 The dictionary URL host name domain matches the parent domain of the + referrer URL host name + 3 The parent domain of the referrer URL host name is not a top level + domain + 4 The dictionary URL is not an HTTPS URL. + */ + // Item (1) above implies item (2). Spec should be updated. + // I take "host name match" to be "is identical to" + if (referring_url.host() != dictionary_url.host()) + return; + if (referring_url.SchemeIs("https")) + return; + if (fetcher_.get()) + fetcher_->Schedule(dictionary_url); +} + +bool SdchManager::AddSdchDictionary(const std::string& dictionary_text, + const GURL& dictionary_url) { + std::string client_hash; + std::string server_hash; + GenerateHash(dictionary_text, &client_hash, &server_hash); + if (dictionaries_.find(server_hash) != dictionaries_.end()) + return false; // Already loaded. + + std::string domain, path; + std::set<int> ports; + Time expiration; + + size_t header_end = dictionary_text.find("\n\n"); + if (std::string::npos == header_end) + return false; // Missing header. + size_t line_start = 0; // Start of line being parsed. + while (1) { + size_t line_end = dictionary_text.find('\n', line_start); + DCHECK(std::string::npos != line_end); + DCHECK(line_end <= header_end); + + size_t colon_index = dictionary_text.find(':', line_start); + if (std::string::npos == colon_index) + return false; // Illegal line missing a colon. + + 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") { + expiration = Time::Now() + TimeDelta::FromSeconds(StringToInt64(value)); + } else if (name == "port") { + int port = StringToInt(value); + 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; + + DHISTOGRAM_COUNTS(L"Sdch.Dictionary size loaded", dictionary_text.size()); + DLOG(INFO) << "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) { + *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; +} + +// TODO(jar): If we have evictions from the dictionaries_, then we need to +// change this interface to return a list of reference counted Dictionary +// instances that can be used if/when a server specifies one. +void SdchManager::GetAvailDictionaryList(const GURL& target_url, + std::string* list) { + for (DictionaryMap::iterator it = dictionaries_.begin(); + it != dictionaries_.end(); ++it) { + if (!it->second->CanAdvertise(target_url)) + continue; + if (!list->empty()) + list->append(","); + list->append(it->second->client_hash()); + } +} + +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 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) { +} + +// static +void SdchManager::GenerateHash(const std::string& dictionary_text, + std::string* client_hash, std::string* server_hash) { + char binary_hash[32]; + base::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(server_hash->length() == 8); + DCHECK(client_hash->length() == 8); +} + +// static +void SdchManager::UrlSafeBase64Encode(const std::string& input, + std::string* output) { + // Since this is only done during a dictionary load, and hashes are only 8 + // characters, we just do the simple fixup, rather than rewriting the encoder. + net::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; + } + } +} + +//------------------------------------------------------------------------------ +// Security functions restricting loads and use of dictionaries. + +// static +int SdchManager::Dictionary::GetPortIncludingDefault(const GURL& url) { + std::string port(url.port()); + if (port.length()) + return StringToInt(port); + if (url.scheme() == "http") + return 80; // Default port value. + // TODO(jar): If sdch supports other schemes, then write a general function + // or surface functionality hidden in url_cannon_stdurl.cc into url_canon.h. + return -1; +} + +// static +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; + /* + A dictionary is invalid and must not be stored if any of the following are + true: + 1. The dictionary has no Domain attribute. + 2. The effective host name that derives from the referer URL host name does + not domain-match the Domain attribute. + 3. The Domain attribute is a top level domain. + 4. The referer URL host is a host domain name (not IP address) and has the + form HD, where D is the value of the Domain attribute, and H is a string + that contains one or more dots. + 5. If the dictionary has a Port attribute and the referer URL's port was not + in the list. + */ + if (domain.empty()) + return false; // Domain is required. + if (0 == + net::RegistryControlledDomainService::GetDomainAndRegistry(domain).size()) + return false; // domain was a TLD. + if (!Dictionary::DomainMatch(dictionary_url, domain)) + return false; + + // TODO(jar): Enforce item 4 above. + + if (!ports.empty() + && 0 == ports.count(GetPortIncludingDefault(dictionary_url))) + return false; + return true; +} + +// static +bool SdchManager::Dictionary::CanUse(const GURL referring_url) { + if (!SdchManager::Global()->IsInSupportedDomain(referring_url)) + return false; + /* + 1. The request URL's host name domain-matches the Domain attribute of the + dictionary. + 2. If the dictionary has a Port attribute, the request port is one of the + ports listed in the Port attribute. + 3. The request URL path-matches the path attribute of the dictionary. + 4. The request is not an HTTPS request. +*/ + if (!DomainMatch(referring_url, domain_)) + return false; + if (!ports_.empty() + && 0 == ports_.count(GetPortIncludingDefault(referring_url))) + return false; + if (path_.size() && !PathMatch(referring_url.path(), path_)) + return false; + if (referring_url.SchemeIsSecure()) + return false; + return true; +} + +bool SdchManager::Dictionary::CanAdvertise(const GURL& target_url) { + if (!SdchManager::Global()->IsInSupportedDomain(target_url)) + return false; + /* The specific rules of when a dictionary should be advertised in an + Avail-Dictionary header are modeled after the rules for cookie scoping. The + terms "domain-match" and "pathmatch" are defined in RFC 2965 [6]. A + dictionary may be advertised in the Avail-Dictionaries header exactly when + all of the following are true: + 1. The server's effective host name domain-matches the Domain attribute of + the dictionary. + 2. If the dictionary has a Port attribute, the request port is one of the + ports listed in the Port attribute. + 3. The request URI path-matches the path header of the dictionary. + 4. The request is not an HTTPS request. + */ + if (!DomainMatch(target_url, domain_)) + return false; + if (!ports_.empty() && 0 == ports_.count(GetPortIncludingDefault(target_url))) + return false; + if (path_.size() && !PathMatch(target_url.path(), path_)) + return false; + if (target_url.SchemeIsSecure()) + return false; + return true; +} + +bool SdchManager::Dictionary::PathMatch(const std::string& path, + const std::string& restriction) { + /* Must be either: + 1. P2 is equal to P1 + 2. P2 is a prefix of P1 and either the final character in P2 is "/" or the + character following P2 in P1 is "/". + */ + if (path == restriction) + return true; + size_t prefix_length = restriction.size(); + if (prefix_length > path.size()) + return false; // Can't be a prefix. + if (0 != restriction.compare(0, prefix_length, path)) + return false; + return restriction[prefix_length - 1] == '/' || path[prefix_length] == '/'; +} + +// static +bool SdchManager::Dictionary::DomainMatch(const GURL& gurl, + const std::string& restriction) { + // TODO(jar): This is not precisely a domain match definition. + return gurl.DomainIs(restriction.data(), restriction.size()); +} |