summaryrefslogtreecommitdiffstats
path: root/net/base/sdch_manager.cc
diff options
context:
space:
mode:
Diffstat (limited to 'net/base/sdch_manager.cc')
-rw-r--r--net/base/sdch_manager.cc336
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());
+}