Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "net/base/sdch_manager.h" | 5 #include "net/base/sdch_manager.h" |
| 6 | 6 |
| 7 #include "base/base64.h" | 7 #include "base/base64.h" |
| 8 #include "base/logging.h" | 8 #include "base/logging.h" |
| 9 #include "base/metrics/histogram.h" | 9 #include "base/metrics/histogram.h" |
| 10 #include "base/strings/string_number_conversions.h" | 10 #include "base/strings/string_number_conversions.h" |
| 11 #include "base/strings/string_util.h" | 11 #include "base/strings/string_util.h" |
| 12 #include "base/values.h" | |
| 12 #include "crypto/sha2.h" | 13 #include "crypto/sha2.h" |
| 13 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" | 14 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| 14 #include "net/url_request/url_request_http_job.h" | 15 #include "net/url_request/url_request_http_job.h" |
| 15 | 16 |
| 16 namespace net { | 17 namespace net { |
| 17 | 18 |
| 18 //------------------------------------------------------------------------------ | 19 //------------------------------------------------------------------------------ |
| 19 // static | 20 // static |
| 20 | 21 |
| 21 // Adjust SDCH limits downwards for mobile. | 22 // Adjust SDCH limits downwards for mobile. |
| 22 #if defined(OS_ANDROID) || defined(OS_IOS) | 23 #if defined(OS_ANDROID) || defined(OS_IOS) |
| 23 // static | 24 // static |
| 24 const size_t SdchManager::kMaxDictionaryCount = 1; | 25 const size_t SdchManager::kMaxDictionaryCount = 1; |
| 25 const size_t SdchManager::kMaxDictionarySize = 150 * 1000; | 26 const size_t SdchManager::kMaxDictionarySize = 150 * 1000; |
| 26 #else | 27 #else |
| 27 // static | 28 // static |
| 28 const size_t SdchManager::kMaxDictionaryCount = 20; | 29 const size_t SdchManager::kMaxDictionaryCount = 20; |
| 29 const size_t SdchManager::kMaxDictionarySize = 1000 * 1000; | 30 const size_t SdchManager::kMaxDictionarySize = 1000 * 1000; |
| 30 #endif | 31 #endif |
| 31 | 32 |
| 32 // static | 33 // static |
| 33 bool SdchManager::g_sdch_enabled_ = true; | 34 bool SdchManager::g_sdch_enabled_ = true; |
| 34 | 35 |
| 35 // static | 36 // static |
| 36 bool SdchManager::g_secure_scheme_supported_ = false; | 37 bool SdchManager::g_secure_scheme_supported_ = false; |
| 37 | 38 |
| 38 //------------------------------------------------------------------------------ | 39 //------------------------------------------------------------------------------ |
| 40 | |
| 41 SdchManager::AddResult::AddResult() : added(false), problem(PROBLEM_CODE_OK) { | |
| 42 } | |
| 43 | |
| 39 SdchManager::Dictionary::Dictionary(const std::string& dictionary_text, | 44 SdchManager::Dictionary::Dictionary(const std::string& dictionary_text, |
| 40 size_t offset, | 45 size_t offset, |
| 41 const std::string& client_hash, | 46 const std::string& client_hash, |
| 42 const GURL& gurl, | 47 const GURL& gurl, |
| 43 const std::string& domain, | 48 const std::string& domain, |
| 44 const std::string& path, | 49 const std::string& path, |
| 45 const base::Time& expiration, | 50 const base::Time& expiration, |
| 46 const std::set<int>& ports) | 51 const std::set<int>& ports) |
| 47 : text_(dictionary_text, offset), | 52 : text_(dictionary_text, offset), |
| 48 client_hash_(client_hash), | 53 client_hash_(client_hash), |
| 49 url_(gurl), | 54 url_(gurl), |
| 50 domain_(domain), | 55 domain_(domain), |
| 51 path_(path), | 56 path_(path), |
| 52 expiration_(expiration), | 57 expiration_(expiration), |
| 53 ports_(ports) { | 58 ports_(ports) { |
| 54 } | 59 } |
| 55 | 60 |
| 56 SdchManager::Dictionary::~Dictionary() { | 61 SdchManager::Dictionary::~Dictionary() { |
| 57 } | 62 } |
| 58 | 63 |
| 59 bool SdchManager::Dictionary::CanAdvertise(const GURL& target_url) { | 64 bool SdchManager::Dictionary::CanAdvertise(const GURL& target_url) const { |
| 60 /* The specific rules of when a dictionary should be advertised in an | 65 /* The specific rules of when a dictionary should be advertised in an |
| 61 Avail-Dictionary header are modeled after the rules for cookie scoping. The | 66 Avail-Dictionary header are modeled after the rules for cookie scoping. The |
| 62 terms "domain-match" and "pathmatch" are defined in RFC 2965 [6]. A | 67 terms "domain-match" and "pathmatch" are defined in RFC 2965 [6]. A |
| 63 dictionary may be advertised in the Avail-Dictionaries header exactly when | 68 dictionary may be advertised in the Avail-Dictionaries header exactly when |
| 64 all of the following are true: | 69 all of the following are true: |
| 65 1. The server's effective host name domain-matches the Domain attribute of | 70 1. The server's effective host name domain-matches the Domain attribute of |
| 66 the dictionary. | 71 the dictionary. |
| 67 2. If the dictionary has a Port attribute, the request port is one of the | 72 2. If the dictionary has a Port attribute, the request port is one of the |
| 68 ports listed in the Port attribute. | 73 ports listed in the Port attribute. |
| 69 3. The request URI path-matches the path header of the dictionary. | 74 3. The request URI path-matches the path header of the dictionary. |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 80 return false; | 85 return false; |
| 81 if (!SdchManager::secure_scheme_supported() && target_url.SchemeIsSecure()) | 86 if (!SdchManager::secure_scheme_supported() && target_url.SchemeIsSecure()) |
| 82 return false; | 87 return false; |
| 83 if (target_url.SchemeIsSecure() != url_.SchemeIsSecure()) | 88 if (target_url.SchemeIsSecure() != url_.SchemeIsSecure()) |
| 84 return false; | 89 return false; |
| 85 if (base::Time::Now() > expiration_) | 90 if (base::Time::Now() > expiration_) |
| 86 return false; | 91 return false; |
| 87 return true; | 92 return true; |
| 88 } | 93 } |
| 89 | 94 |
| 95 base::DictionaryValue* SdchManager::Dictionary::DictionaryInfoToValue() const { | |
| 96 base::DictionaryValue* value = new base::DictionaryValue(); | |
| 97 value->SetString("url", url_.spec()); | |
| 98 value->SetString("client_hash", client_hash_); | |
| 99 value->SetString("domain", domain_); | |
| 100 value->SetString("path", path_); | |
| 101 value->SetString("expiration", | |
| 102 base::Int64ToString(expiration_.ToInternalValue())); | |
| 103 base::ListValue* port_list = new base::ListValue(); | |
| 104 for (std::set<int>::const_iterator it = ports_.begin(); it != ports_.end(); | |
| 105 ++it) { | |
| 106 port_list->AppendInteger(*it); | |
| 107 } | |
| 108 value->Set("ports", port_list); | |
| 109 return value; | |
| 110 } | |
| 111 | |
| 90 //------------------------------------------------------------------------------ | 112 //------------------------------------------------------------------------------ |
| 91 // Security functions restricting loads and use of dictionaries. | 113 // Security functions restricting loads and use of dictionaries. |
| 92 | 114 |
| 93 // static | 115 // static |
| 94 bool SdchManager::Dictionary::CanSet(const std::string& domain, | 116 SdchManager::ProblemCodes SdchManager::Dictionary::CanSet( |
| 95 const std::string& path, | 117 const std::string& domain, |
| 96 const std::set<int>& ports, | 118 const std::string& path, |
| 97 const GURL& dictionary_url) { | 119 const std::set<int>& ports, |
| 120 const GURL& dictionary_url) { | |
| 98 /* | 121 /* |
| 99 A dictionary is invalid and must not be stored if any of the following are | 122 A dictionary is invalid and must not be stored if any of the following are |
| 100 true: | 123 true: |
| 101 1. The dictionary has no Domain attribute. | 124 1. The dictionary has no Domain attribute. |
| 102 2. The effective host name that derives from the referer URL host name does | 125 2. The effective host name that derives from the referer URL host name does |
| 103 not domain-match the Domain attribute. | 126 not domain-match the Domain attribute. |
| 104 3. The Domain attribute is a top level domain. | 127 3. The Domain attribute is a top level domain. |
| 105 4. The referer URL host is a host domain name (not IP address) and has the | 128 4. The referer URL host is a host domain name (not IP address) and has the |
| 106 form HD, where D is the value of the Domain attribute, and H is a string | 129 form HD, where D is the value of the Domain attribute, and H is a string |
| 107 that contains one or more dots. | 130 that contains one or more dots. |
| 108 5. If the dictionary has a Port attribute and the referer URL's port was not | 131 5. If the dictionary has a Port attribute and the referer URL's port was not |
| 109 in the list. | 132 in the list. |
| 110 */ | 133 */ |
| 111 | 134 |
| 112 // TODO(jar): Redirects in dictionary fetches might plausibly be problematic, | 135 // TODO(jar): Redirects in dictionary fetches might plausibly be problematic, |
| 113 // and hence the conservative approach is to not allow any redirects (if there | 136 // and hence the conservative approach is to not allow any redirects (if there |
| 114 // were any... then don't allow the dictionary to be set). | 137 // were any... then don't allow the dictionary to be set). |
| 115 | 138 |
| 116 if (domain.empty()) { | 139 if (domain.empty()) |
| 117 SdchErrorRecovery(DICTIONARY_MISSING_DOMAIN_SPECIFIER); | 140 return DICTIONARY_MISSING_DOMAIN_SPECIFIER; // Domain is required. |
| 118 return false; // Domain is required. | 141 |
| 119 } | |
| 120 if (registry_controlled_domains::GetDomainAndRegistry( | 142 if (registry_controlled_domains::GetDomainAndRegistry( |
| 121 domain, | 143 domain, registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES) |
| 122 registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES).empty()) { | 144 .empty()) |
| 123 SdchErrorRecovery(DICTIONARY_SPECIFIES_TOP_LEVEL_DOMAIN); | 145 return DICTIONARY_SPECIFIES_TOP_LEVEL_DOMAIN; // domain was a TLD. |
| 124 return false; // domain was a TLD. | 146 |
| 125 } | 147 if (!Dictionary::DomainMatch(dictionary_url, domain)) |
| 126 if (!Dictionary::DomainMatch(dictionary_url, domain)) { | 148 return DICTIONARY_DOMAIN_NOT_MATCHING_SOURCE_URL; |
| 127 SdchErrorRecovery(DICTIONARY_DOMAIN_NOT_MATCHING_SOURCE_URL); | |
| 128 return false; | |
| 129 } | |
| 130 | 149 |
| 131 std::string referrer_url_host = dictionary_url.host(); | 150 std::string referrer_url_host = dictionary_url.host(); |
| 132 size_t postfix_domain_index = referrer_url_host.rfind(domain); | 151 size_t postfix_domain_index = referrer_url_host.rfind(domain); |
| 133 // See if it is indeed a postfix, or just an internal string. | 152 // See if it is indeed a postfix, or just an internal string. |
| 134 if (referrer_url_host.size() == postfix_domain_index + domain.size()) { | 153 if (referrer_url_host.size() == postfix_domain_index + domain.size()) { |
| 135 // It is a postfix... so check to see if there's a dot in the prefix. | 154 // It is a postfix... so check to see if there's a dot in the prefix. |
| 136 size_t end_of_host_index = referrer_url_host.find_first_of('.'); | 155 size_t end_of_host_index = referrer_url_host.find_first_of('.'); |
| 137 if (referrer_url_host.npos != end_of_host_index && | 156 if (referrer_url_host.npos != end_of_host_index && |
| 138 end_of_host_index < postfix_domain_index) { | 157 end_of_host_index < postfix_domain_index) |
| 139 SdchErrorRecovery(DICTIONARY_REFERER_URL_HAS_DOT_IN_PREFIX); | 158 return DICTIONARY_REFERER_URL_HAS_DOT_IN_PREFIX; |
| 140 return false; | |
| 141 } | |
| 142 } | 159 } |
| 143 | 160 |
| 144 if (!ports.empty() | 161 if (!ports.empty() && 0 == ports.count(dictionary_url.EffectiveIntPort())) |
| 145 && 0 == ports.count(dictionary_url.EffectiveIntPort())) { | 162 return DICTIONARY_PORT_NOT_MATCHING_SOURCE_URL; |
| 146 SdchErrorRecovery(DICTIONARY_PORT_NOT_MATCHING_SOURCE_URL); | 163 |
| 147 return false; | 164 return PROBLEM_CODE_OK; |
| 148 } | |
| 149 return true; | |
| 150 } | 165 } |
| 151 | 166 |
| 152 // static | 167 SdchManager::ProblemCodes SdchManager::Dictionary::CanUse( |
| 153 bool SdchManager::Dictionary::CanUse(const GURL& referring_url) { | 168 const GURL& referring_url) const { |
| 154 /* | 169 /* |
| 155 1. The request URL's host name domain-matches the Domain attribute of the | 170 1. The request URL's host name domain-matches the Domain attribute of the |
| 156 dictionary. | 171 dictionary. |
| 157 2. If the dictionary has a Port attribute, the request port is one of the | 172 2. If the dictionary has a Port attribute, the request port is one of the |
| 158 ports listed in the Port attribute. | 173 ports listed in the Port attribute. |
| 159 3. The request URL path-matches the path attribute of the dictionary. | 174 3. The request URL path-matches the path attribute of the dictionary. |
| 160 4. The request is not an HTTPS request. | 175 4. The request is not an HTTPS request. |
| 161 We can override (ignore) item (4) only when we have explicitly enabled | 176 We can override (ignore) item (4) only when we have explicitly enabled |
| 162 HTTPS support AND the dictionary acquisition scheme matches the target | 177 HTTPS support AND the dictionary acquisition scheme matches the target |
| 163 url scheme. | 178 url scheme. |
| 164 */ | 179 */ |
| 165 if (!DomainMatch(referring_url, domain_)) { | 180 if (!DomainMatch(referring_url, domain_)) |
| 166 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_DOMAIN); | 181 return DICTIONARY_FOUND_HAS_WRONG_DOMAIN; |
| 167 return false; | 182 |
| 168 } | 183 if (!ports_.empty() && 0 == ports_.count(referring_url.EffectiveIntPort())) |
| 169 if (!ports_.empty() | 184 return DICTIONARY_FOUND_HAS_WRONG_PORT_LIST; |
| 170 && 0 == ports_.count(referring_url.EffectiveIntPort())) { | 185 |
| 171 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_PORT_LIST); | 186 if (path_.size() && !PathMatch(referring_url.path(), path_)) |
| 172 return false; | 187 return DICTIONARY_FOUND_HAS_WRONG_PATH; |
| 173 } | 188 |
| 174 if (path_.size() && !PathMatch(referring_url.path(), path_)) { | 189 if (!SdchManager::secure_scheme_supported() && referring_url.SchemeIsSecure()) |
| 175 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_PATH); | 190 return DICTIONARY_FOUND_HAS_WRONG_SCHEME; |
| 176 return false; | 191 |
| 177 } | 192 if (referring_url.SchemeIsSecure() != url_.SchemeIsSecure()) |
| 178 if (!SdchManager::secure_scheme_supported() && | 193 return DICTIONARY_FOUND_HAS_WRONG_SCHEME; |
| 179 referring_url.SchemeIsSecure()) { | |
| 180 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_SCHEME); | |
| 181 return false; | |
| 182 } | |
| 183 if (referring_url.SchemeIsSecure() != url_.SchemeIsSecure()) { | |
| 184 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_SCHEME); | |
| 185 return false; | |
| 186 } | |
| 187 | 194 |
| 188 // TODO(jar): Remove overly restrictive failsafe test (added per security | 195 // TODO(jar): Remove overly restrictive failsafe test (added per security |
| 189 // review) when we have a need to be more general. | 196 // review) when we have a need to be more general. |
| 190 if (!referring_url.SchemeIsHTTPOrHTTPS()) { | 197 if (!referring_url.SchemeIsHTTPOrHTTPS()) |
| 191 SdchErrorRecovery(ATTEMPT_TO_DECODE_NON_HTTP_DATA); | 198 return ATTEMPT_TO_DECODE_NON_HTTP_DATA; |
| 192 return false; | |
| 193 } | |
| 194 | 199 |
| 195 return true; | 200 return PROBLEM_CODE_OK; |
| 196 } | 201 } |
| 197 | 202 |
| 203 // static | |
| 198 bool SdchManager::Dictionary::PathMatch(const std::string& path, | 204 bool SdchManager::Dictionary::PathMatch(const std::string& path, |
| 199 const std::string& restriction) { | 205 const std::string& restriction) { |
| 200 /* Must be either: | 206 /* Must be either: |
| 201 1. P2 is equal to P1 | 207 1. P2 is equal to P1 |
| 202 2. P2 is a prefix of P1 and either the final character in P2 is "/" or the | 208 2. P2 is a prefix of P1 and either the final character in P2 is "/" or the |
| 203 character following P2 in P1 is "/". | 209 character following P2 in P1 is "/". |
| 204 */ | 210 */ |
| 205 if (path == restriction) | 211 if (path == restriction) |
| 206 return true; | 212 return true; |
| 207 size_t prefix_length = restriction.size(); | 213 size_t prefix_length = restriction.size(); |
| (...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 308 | 314 |
| 309 int SdchManager::BlacklistDomainExponential(const std::string& domain) { | 315 int SdchManager::BlacklistDomainExponential(const std::string& domain) { |
| 310 if (exponential_blacklist_count_.end() == | 316 if (exponential_blacklist_count_.end() == |
| 311 exponential_blacklist_count_.find(domain)) | 317 exponential_blacklist_count_.find(domain)) |
| 312 return 0; | 318 return 0; |
| 313 return exponential_blacklist_count_[StringToLowerASCII(domain)]; | 319 return exponential_blacklist_count_[StringToLowerASCII(domain)]; |
| 314 } | 320 } |
| 315 | 321 |
| 316 bool SdchManager::IsInSupportedDomain(const GURL& url) { | 322 bool SdchManager::IsInSupportedDomain(const GURL& url) { |
| 317 DCHECK(CalledOnValidThread()); | 323 DCHECK(CalledOnValidThread()); |
| 324 if (!IsSdchEnabledForUrl(url)) | |
|
baranovich
2014/08/02 21:17:24
I've split this method into two for 2 reasons:
1)
| |
| 325 return false; | |
| 326 | |
| 327 if (IsInBlacklistedDomain(url)) { | |
| 328 SdchErrorRecovery(DOMAIN_BLACKLIST_INCLUDES_TARGET); | |
| 329 return false; | |
| 330 } | |
| 331 return true; | |
| 332 } | |
| 333 | |
| 334 // static | |
| 335 bool SdchManager::IsSdchEnabledForUrl(const GURL& url) { | |
| 318 if (!g_sdch_enabled_ ) | 336 if (!g_sdch_enabled_ ) |
| 319 return false; | 337 return false; |
| 320 | 338 |
| 321 if (!secure_scheme_supported() && url.SchemeIsSecure()) | 339 if (!secure_scheme_supported() && url.SchemeIsSecure()) |
| 322 return false; | 340 return false; |
| 323 | 341 |
| 342 return true; | |
| 343 } | |
| 344 | |
| 345 bool SdchManager::IsInBlacklistedDomain(const GURL& url) { | |
| 324 if (blacklisted_domains_.empty()) | 346 if (blacklisted_domains_.empty()) |
| 325 return true; | 347 return false; |
| 326 | 348 |
| 327 std::string domain(StringToLowerASCII(url.host())); | 349 std::string domain(StringToLowerASCII(url.host())); |
| 328 DomainCounter::iterator it = blacklisted_domains_.find(domain); | 350 DomainCounter::iterator it = blacklisted_domains_.find(domain); |
| 329 if (blacklisted_domains_.end() == it) | 351 if (blacklisted_domains_.end() == it) |
| 330 return true; | 352 return false; |
| 331 | 353 |
| 332 int count = it->second - 1; | 354 int count = it->second - 1; |
| 333 if (count > 0) | 355 if (count > 0) |
| 334 blacklisted_domains_[domain] = count; | 356 blacklisted_domains_[domain] = count; |
| 335 else | 357 else |
| 336 blacklisted_domains_.erase(domain); | 358 blacklisted_domains_.erase(domain); |
| 337 SdchErrorRecovery(DOMAIN_BLACKLIST_INCLUDES_TARGET); | 359 |
| 338 return false; | 360 return true; |
| 339 } | 361 } |
| 340 | 362 |
| 341 void SdchManager::FetchDictionary(const GURL& request_url, | 363 SdchManager::ProblemCodes SdchManager::FetchDictionary( |
| 342 const GURL& dictionary_url) { | 364 const GURL& request_url, |
| 365 const GURL& dictionary_url) { | |
| 343 DCHECK(CalledOnValidThread()); | 366 DCHECK(CalledOnValidThread()); |
| 344 if (CanFetchDictionary(request_url, dictionary_url) && fetcher_.get()) | 367 ProblemCodes rv = CanFetchDictionary(request_url, dictionary_url); |
| 368 if (rv != PROBLEM_CODE_OK) | |
| 369 return rv; | |
| 370 | |
| 371 if (fetcher_.get()) | |
| 345 fetcher_->Schedule(dictionary_url); | 372 fetcher_->Schedule(dictionary_url); |
| 373 | |
| 374 return PROBLEM_CODE_OK; | |
| 346 } | 375 } |
| 347 | 376 |
| 348 bool SdchManager::CanFetchDictionary(const GURL& referring_url, | 377 SdchManager::ProblemCodes SdchManager::CanFetchDictionary( |
| 349 const GURL& dictionary_url) const { | 378 const GURL& referring_url, |
| 379 const GURL& dictionary_url) const { | |
| 350 DCHECK(CalledOnValidThread()); | 380 DCHECK(CalledOnValidThread()); |
| 351 /* The user agent may retrieve a dictionary from the dictionary URL if all of | 381 /* The user agent may retrieve a dictionary from the dictionary URL if all of |
| 352 the following are true: | 382 the following are true: |
| 353 1 The dictionary URL host name matches the referrer URL host name and | 383 1 The dictionary URL host name matches the referrer URL host name and |
| 354 scheme. | 384 scheme. |
| 355 2 The dictionary URL host name domain matches the parent domain of the | 385 2 The dictionary URL host name domain matches the parent domain of the |
| 356 referrer URL host name | 386 referrer URL host name |
| 357 3 The parent domain of the referrer URL host name is not a top level | 387 3 The parent domain of the referrer URL host name is not a top level |
| 358 domain | 388 domain |
| 359 4 The dictionary URL is not an HTTPS URL. | 389 4 The dictionary URL is not an HTTPS URL. |
| 360 */ | 390 */ |
| 361 // Item (1) above implies item (2). Spec should be updated. | 391 // Item (1) above implies item (2). Spec should be updated. |
| 362 // I take "host name match" to be "is identical to" | 392 // I take "host name match" to be "is identical to" |
| 363 if (referring_url.host() != dictionary_url.host() || | 393 if (referring_url.host() != dictionary_url.host() || |
| 364 referring_url.scheme() != dictionary_url.scheme()) { | 394 referring_url.scheme() != dictionary_url.scheme()) |
| 365 SdchErrorRecovery(DICTIONARY_LOAD_ATTEMPT_FROM_DIFFERENT_HOST); | 395 return DICTIONARY_LOAD_ATTEMPT_FROM_DIFFERENT_HOST; |
| 366 return false; | 396 |
| 367 } | 397 if (!secure_scheme_supported() && referring_url.SchemeIsSecure()) |
| 368 if (!secure_scheme_supported() && referring_url.SchemeIsSecure()) { | 398 return DICTIONARY_SELECTED_FOR_SSL; |
| 369 SdchErrorRecovery(DICTIONARY_SELECTED_FOR_SSL); | |
| 370 return false; | |
| 371 } | |
| 372 | 399 |
| 373 // TODO(jar): Remove this failsafe conservative hack which is more restrictive | 400 // TODO(jar): Remove this failsafe conservative hack which is more restrictive |
| 374 // than current SDCH spec when needed, and justified by security audit. | 401 // than current SDCH spec when needed, and justified by security audit. |
| 375 if (!referring_url.SchemeIsHTTPOrHTTPS()) { | 402 if (!referring_url.SchemeIsHTTPOrHTTPS()) |
| 376 SdchErrorRecovery(DICTIONARY_SELECTED_FROM_NON_HTTP); | 403 return DICTIONARY_SELECTED_FROM_NON_HTTP; |
| 377 return false; | |
| 378 } | |
| 379 | 404 |
| 380 return true; | 405 return PROBLEM_CODE_OK; |
| 381 } | 406 } |
| 382 | 407 |
| 383 bool SdchManager::AddSdchDictionary(const std::string& dictionary_text, | 408 SdchManager::AddResult SdchManager::AddSdchDictionary( |
| 409 const std::string& dictionary_text, | |
| 384 const GURL& dictionary_url) { | 410 const GURL& dictionary_url) { |
| 385 DCHECK(CalledOnValidThread()); | 411 DCHECK(CalledOnValidThread()); |
| 386 std::string client_hash; | 412 std::string client_hash; |
| 387 std::string server_hash; | 413 std::string server_hash; |
| 388 GenerateHash(dictionary_text, &client_hash, &server_hash); | 414 GenerateHash(dictionary_text, &client_hash, &server_hash); |
| 415 AddResult rv; | |
| 389 if (dictionaries_.find(server_hash) != dictionaries_.end()) { | 416 if (dictionaries_.find(server_hash) != dictionaries_.end()) { |
| 390 SdchErrorRecovery(DICTIONARY_ALREADY_LOADED); | 417 rv.problem = DICTIONARY_ALREADY_LOADED; // Already loaded. |
| 391 return false; // Already loaded. | 418 return rv; |
| 392 } | 419 } |
| 393 | 420 |
| 394 std::string domain, path; | 421 std::string domain, path; |
| 395 std::set<int> ports; | 422 std::set<int> ports; |
| 396 base::Time expiration(base::Time::Now() + base::TimeDelta::FromDays(30)); | 423 base::Time expiration(base::Time::Now() + base::TimeDelta::FromDays(30)); |
| 397 | 424 |
| 398 if (dictionary_text.empty()) { | 425 if (dictionary_text.empty()) { |
| 399 SdchErrorRecovery(DICTIONARY_HAS_NO_TEXT); | 426 rv.problem = DICTIONARY_HAS_NO_TEXT; // Empty response. |
| 400 return false; // Missing header. | 427 return rv; |
| 401 } | 428 } |
| 402 | 429 |
| 403 size_t header_end = dictionary_text.find("\n\n"); | 430 size_t header_end = dictionary_text.find("\n\n"); |
| 404 if (std::string::npos == header_end) { | 431 if (std::string::npos == header_end) { |
| 405 SdchErrorRecovery(DICTIONARY_HAS_NO_HEADER); | 432 rv.problem = DICTIONARY_HAS_NO_HEADER; // Missing header. |
| 406 return false; // Missing header. | 433 return rv; |
| 407 } | 434 } |
| 435 | |
| 408 size_t line_start = 0; // Start of line being parsed. | 436 size_t line_start = 0; // Start of line being parsed. |
| 409 while (1) { | 437 while (1) { |
| 410 size_t line_end = dictionary_text.find('\n', line_start); | 438 size_t line_end = dictionary_text.find('\n', line_start); |
| 411 DCHECK(std::string::npos != line_end); | 439 DCHECK(std::string::npos != line_end); |
| 412 DCHECK_LE(line_end, header_end); | 440 DCHECK_LE(line_end, header_end); |
| 413 | 441 |
| 414 size_t colon_index = dictionary_text.find(':', line_start); | 442 size_t colon_index = dictionary_text.find(':', line_start); |
| 415 if (std::string::npos == colon_index) { | 443 if (std::string::npos == colon_index) { |
| 416 SdchErrorRecovery(DICTIONARY_HEADER_LINE_MISSING_COLON); | 444 // Illegal line missing a colon. |
| 417 return false; // Illegal line missing a colon. | 445 rv.problem = DICTIONARY_HEADER_LINE_MISSING_COLON; |
| 446 return rv; | |
| 418 } | 447 } |
| 419 | 448 |
| 420 if (colon_index > line_end) | 449 if (colon_index > line_end) |
| 421 break; | 450 break; |
| 422 | 451 |
| 423 size_t value_start = dictionary_text.find_first_not_of(" \t", | 452 size_t value_start = dictionary_text.find_first_not_of(" \t", |
| 424 colon_index + 1); | 453 colon_index + 1); |
| 425 if (std::string::npos != value_start) { | 454 if (std::string::npos != value_start) { |
| 426 if (value_start >= line_end) | 455 if (value_start >= line_end) |
| 427 break; | 456 break; |
| 428 std::string name(dictionary_text, line_start, colon_index - line_start); | 457 std::string name(dictionary_text, line_start, colon_index - line_start); |
| 429 std::string value(dictionary_text, value_start, line_end - value_start); | 458 std::string value(dictionary_text, value_start, line_end - value_start); |
| 430 name = StringToLowerASCII(name); | 459 name = StringToLowerASCII(name); |
| 431 if (name == "domain") { | 460 if (name == "domain") { |
| 432 domain = value; | 461 domain = value; |
| 433 } else if (name == "path") { | 462 } else if (name == "path") { |
| 434 path = value; | 463 path = value; |
| 435 } else if (name == "format-version") { | 464 } else if (name == "format-version") { |
| 436 if (value != "1.0") | 465 if (value != "1.0") { |
| 437 return false; | 466 // TODO(baranovich): add problem code to track unsupported versions? |
| 467 return rv; | |
| 468 } | |
| 438 } else if (name == "max-age") { | 469 } else if (name == "max-age") { |
| 439 int64 seconds; | 470 int64 seconds; |
| 440 base::StringToInt64(value, &seconds); | 471 base::StringToInt64(value, &seconds); |
| 441 expiration = base::Time::Now() + base::TimeDelta::FromSeconds(seconds); | 472 expiration = base::Time::Now() + base::TimeDelta::FromSeconds(seconds); |
| 442 } else if (name == "port") { | 473 } else if (name == "port") { |
| 443 int port; | 474 int port; |
| 444 base::StringToInt(value, &port); | 475 base::StringToInt(value, &port); |
| 445 if (port >= 0) | 476 if (port >= 0) |
| 446 ports.insert(port); | 477 ports.insert(port); |
| 447 } | 478 } |
| 448 } | 479 } |
| 449 | 480 |
| 450 if (line_end >= header_end) | 481 if (line_end >= header_end) |
| 451 break; | 482 break; |
| 452 line_start = line_end + 1; | 483 line_start = line_end + 1; |
| 453 } | 484 } |
| 454 | 485 |
| 455 if (!IsInSupportedDomain(dictionary_url)) | 486 if (!IsSdchEnabledForUrl(dictionary_url)) |
| 456 return false; | 487 return rv; |
| 457 | 488 |
| 458 if (!Dictionary::CanSet(domain, path, ports, dictionary_url)) | 489 if (IsInBlacklistedDomain(dictionary_url)) { |
| 459 return false; | 490 rv.problem = DOMAIN_BLACKLIST_INCLUDES_TARGET; |
| 491 return rv; | |
| 492 } | |
| 493 | |
| 494 ProblemCodes err = Dictionary::CanSet(domain, path, ports, dictionary_url); | |
| 495 if (err != PROBLEM_CODE_OK) { | |
| 496 rv.problem = err; | |
| 497 return rv; | |
| 498 } | |
| 460 | 499 |
| 461 // TODO(jar): Remove these hacks to preclude a DOS attack involving piles of | 500 // TODO(jar): Remove these hacks to preclude a DOS attack involving piles of |
| 462 // useless dictionaries. We should probably have a cache eviction plan, | 501 // useless dictionaries. We should probably have a cache eviction plan, |
| 463 // instead of just blocking additions. For now, with the spec in flux, it | 502 // instead of just blocking additions. For now, with the spec in flux, it |
| 464 // is probably not worth doing eviction handling. | 503 // is probably not worth doing eviction handling. |
| 465 if (kMaxDictionarySize < dictionary_text.size()) { | 504 if (kMaxDictionarySize < dictionary_text.size()) { |
| 466 SdchErrorRecovery(DICTIONARY_IS_TOO_LARGE); | 505 rv.problem = DICTIONARY_IS_TOO_LARGE; |
| 467 return false; | 506 return rv; |
| 468 } | 507 } |
| 469 if (kMaxDictionaryCount <= dictionaries_.size()) { | 508 if (kMaxDictionaryCount <= dictionaries_.size()) { |
| 470 SdchErrorRecovery(DICTIONARY_COUNT_EXCEEDED); | 509 rv.problem = DICTIONARY_COUNT_EXCEEDED; |
| 471 return false; | 510 return rv; |
| 472 } | 511 } |
| 473 | 512 |
| 474 UMA_HISTOGRAM_COUNTS("Sdch3.Dictionary size loaded", dictionary_text.size()); | 513 UMA_HISTOGRAM_COUNTS("Sdch3.Dictionary size loaded", dictionary_text.size()); |
| 475 DVLOG(1) << "Loaded dictionary with client hash " << client_hash | 514 DVLOG(1) << "Loaded dictionary with client hash " << client_hash |
| 476 << " and server hash " << server_hash; | 515 << " and server hash " << server_hash; |
| 477 Dictionary* dictionary = | 516 Dictionary* dictionary = |
| 478 new Dictionary(dictionary_text, header_end + 2, client_hash, | 517 new Dictionary(dictionary_text, header_end + 2, client_hash, |
| 479 dictionary_url, domain, path, expiration, ports); | 518 dictionary_url, domain, path, expiration, ports); |
| 480 dictionaries_[server_hash] = dictionary; | 519 dictionaries_[server_hash] = dictionary; |
| 481 return true; | 520 |
| 521 rv.added = true; | |
| 522 return rv; | |
| 482 } | 523 } |
| 483 | 524 |
| 484 void SdchManager::GetVcdiffDictionary( | 525 SdchManager::ProblemCodes SdchManager::GetVcdiffDictionary( |
| 485 const std::string& server_hash, | 526 const std::string& server_hash, |
| 486 const GURL& referring_url, | 527 const GURL& referring_url, |
| 487 scoped_refptr<Dictionary>* dictionary) { | 528 scoped_refptr<Dictionary>* dictionary) { |
| 488 DCHECK(CalledOnValidThread()); | 529 DCHECK(CalledOnValidThread()); |
| 489 *dictionary = NULL; | 530 *dictionary = NULL; |
| 490 DictionaryMap::iterator it = dictionaries_.find(server_hash); | 531 DictionaryMap::iterator it = dictionaries_.find(server_hash); |
| 491 if (it == dictionaries_.end()) { | 532 if (it == dictionaries_.end()) |
| 492 return; | 533 return PROBLEM_CODE_OK; // not a problem. Just no dictionary. |
| 493 } | 534 |
| 494 scoped_refptr<Dictionary> matching_dictionary = it->second; | 535 scoped_refptr<Dictionary> matching_dictionary = it->second; |
| 495 if (!IsInSupportedDomain(referring_url)) | 536 if (!IsSdchEnabledForUrl(referring_url)) |
| 496 return; | 537 return PROBLEM_CODE_OK; // not a problem. SDCH is disabled. |
| 497 if (!matching_dictionary->CanUse(referring_url)) | 538 |
| 498 return; | 539 if (IsInBlacklistedDomain(referring_url)) |
| 499 *dictionary = matching_dictionary; | 540 return DOMAIN_BLACKLIST_INCLUDES_TARGET; |
| 541 | |
| 542 ProblemCodes rv = matching_dictionary->CanUse(referring_url); | |
| 543 if (rv == PROBLEM_CODE_OK) | |
| 544 *dictionary = matching_dictionary; | |
| 545 return rv; | |
| 500 } | 546 } |
| 501 | 547 |
| 502 // TODO(jar): If we have evictions from the dictionaries_, then we need to | 548 // TODO(jar): If we have evictions from the dictionaries_, then we need to |
| 503 // change this interface to return a list of reference counted Dictionary | 549 // change this interface to return a list of reference counted Dictionary |
| 504 // instances that can be used if/when a server specifies one. | 550 // instances that can be used if/when a server specifies one. |
| 505 void SdchManager::GetAvailDictionaryList(const GURL& target_url, | 551 void SdchManager::GetAvailDictionaryList(const GURL& target_url, |
| 506 std::string* list) { | 552 std::string* list) { |
| 507 DCHECK(CalledOnValidThread()); | 553 DCHECK(CalledOnValidThread()); |
| 508 int count = 0; | 554 int count = 0; |
| 509 for (DictionaryMap::iterator it = dictionaries_.begin(); | 555 for (DictionaryMap::iterator it = dictionaries_.begin(); |
| 510 it != dictionaries_.end(); ++it) { | 556 it != dictionaries_.end(); ++it) { |
| 511 if (!IsInSupportedDomain(target_url)) | 557 if (!IsInSupportedDomain(target_url)) { |
| 512 continue; | 558 continue; |
| 559 } | |
| 513 if (!it->second->CanAdvertise(target_url)) | 560 if (!it->second->CanAdvertise(target_url)) |
| 514 continue; | 561 continue; |
| 515 ++count; | 562 ++count; |
| 516 if (!list->empty()) | 563 if (!list->empty()) |
| 517 list->append(","); | 564 list->append(","); |
| 518 list->append(it->second->client_hash()); | 565 list->append(it->second->client_hash()); |
| 519 } | 566 } |
| 520 // Watch to see if we have corrupt or numerous dictionaries. | 567 // Watch to see if we have corrupt or numerous dictionaries. |
| 521 if (count > 0) | 568 if (count > 0) |
| 522 UMA_HISTOGRAM_COUNTS("Sdch3.Advertisement_Count", count); | 569 UMA_HISTOGRAM_COUNTS("Sdch3.Advertisement_Count", count); |
| 523 } | 570 } |
| 524 | 571 |
| 525 // static | 572 // static |
| 526 void SdchManager::GenerateHash(const std::string& dictionary_text, | 573 void SdchManager::GenerateHash(const std::string& dictionary_text, |
| 527 std::string* client_hash, std::string* server_hash) { | 574 std::string* client_hash, std::string* server_hash) { |
| 528 char binary_hash[32]; | 575 char binary_hash[32]; |
| 529 crypto::SHA256HashString(dictionary_text, binary_hash, sizeof(binary_hash)); | 576 crypto::SHA256HashString(dictionary_text, binary_hash, sizeof(binary_hash)); |
| 530 | 577 |
| 531 std::string first_48_bits(&binary_hash[0], 6); | 578 std::string first_48_bits(&binary_hash[0], 6); |
| 532 std::string second_48_bits(&binary_hash[6], 6); | 579 std::string second_48_bits(&binary_hash[6], 6); |
| 533 UrlSafeBase64Encode(first_48_bits, client_hash); | 580 UrlSafeBase64Encode(first_48_bits, client_hash); |
| 534 UrlSafeBase64Encode(second_48_bits, server_hash); | 581 UrlSafeBase64Encode(second_48_bits, server_hash); |
| 535 | 582 |
| 536 DCHECK_EQ(server_hash->length(), 8u); | 583 DCHECK_EQ(server_hash->length(), 8u); |
| 537 DCHECK_EQ(client_hash->length(), 8u); | 584 DCHECK_EQ(client_hash->length(), 8u); |
| 538 } | 585 } |
| 539 | 586 |
| 587 base::Value* SdchManager::SdchInfoToValue() const { | |
| 588 base::DictionaryValue* value = new base::DictionaryValue(); | |
| 589 | |
| 590 value->SetBoolean("sdch_enabled", sdch_enabled()); | |
| 591 value->SetBoolean("secure_scheme_support", secure_scheme_supported()); | |
| 592 | |
| 593 base::ListValue* entry_list = new base::ListValue(); | |
| 594 for (DictionaryMap::const_iterator it = dictionaries_.begin(); | |
| 595 it != dictionaries_.end(); | |
| 596 ++it) { | |
| 597 base::DictionaryValue* entry_dict = it->second->DictionaryInfoToValue(); | |
| 598 entry_dict->SetString("server_hash", it->first); | |
| 599 entry_list->Append(entry_dict); | |
| 600 } | |
| 601 value->Set("dictionaries", entry_list); | |
| 602 | |
| 603 entry_list = new base::ListValue(); | |
| 604 for (DomainCounter::const_iterator it = blacklisted_domains_.begin(); | |
| 605 it != blacklisted_domains_.end(); | |
| 606 ++it) { | |
| 607 base::DictionaryValue* entry_dict = new base::DictionaryValue(); | |
| 608 entry_dict->SetString("domain", it->first); | |
| 609 if (it->second != INT_MAX) | |
| 610 entry_dict->SetInteger("tries", it->second); | |
| 611 entry_list->Append(entry_dict); | |
| 612 } | |
| 613 value->Set("blacklisted", entry_list); | |
| 614 | |
| 615 return value; | |
| 616 } | |
| 617 | |
| 540 //------------------------------------------------------------------------------ | 618 //------------------------------------------------------------------------------ |
| 541 // Methods for supporting latency experiments. | 619 // Methods for supporting latency experiments. |
| 542 | 620 |
| 543 bool SdchManager::AllowLatencyExperiment(const GURL& url) const { | 621 bool SdchManager::AllowLatencyExperiment(const GURL& url) const { |
| 544 DCHECK(CalledOnValidThread()); | 622 DCHECK(CalledOnValidThread()); |
| 545 return allow_latency_experiment_.end() != | 623 return allow_latency_experiment_.end() != |
| 546 allow_latency_experiment_.find(url.host()); | 624 allow_latency_experiment_.find(url.host()); |
| 547 } | 625 } |
| 548 | 626 |
| 549 void SdchManager::SetAllowLatencyExperiment(const GURL& url, bool enable) { | 627 void SdchManager::SetAllowLatencyExperiment(const GURL& url, bool enable) { |
| 550 DCHECK(CalledOnValidThread()); | 628 DCHECK(CalledOnValidThread()); |
| 551 if (enable) { | 629 if (enable) { |
| 552 allow_latency_experiment_.insert(url.host()); | 630 allow_latency_experiment_.insert(url.host()); |
| 553 return; | 631 return; |
| 554 } | 632 } |
| 555 ExperimentSet::iterator it = allow_latency_experiment_.find(url.host()); | 633 ExperimentSet::iterator it = allow_latency_experiment_.find(url.host()); |
| 556 if (allow_latency_experiment_.end() == it) | 634 if (allow_latency_experiment_.end() == it) |
| 557 return; // It was already erased, or never allowed. | 635 return; // It was already erased, or never allowed. |
| 636 | |
| 637 // Do not send error up. It's internal stuff, I guess. | |
| 558 SdchErrorRecovery(LATENCY_TEST_DISALLOWED); | 638 SdchErrorRecovery(LATENCY_TEST_DISALLOWED); |
| 559 allow_latency_experiment_.erase(it); | 639 allow_latency_experiment_.erase(it); |
| 560 } | 640 } |
| 561 | 641 |
| 562 // static | 642 // static |
| 563 void SdchManager::UrlSafeBase64Encode(const std::string& input, | 643 void SdchManager::UrlSafeBase64Encode(const std::string& input, |
| 564 std::string* output) { | 644 std::string* output) { |
| 565 // Since this is only done during a dictionary load, and hashes are only 8 | 645 // Since this is only done during a dictionary load, and hashes are only 8 |
| 566 // characters, we just do the simple fixup, rather than rewriting the encoder. | 646 // characters, we just do the simple fixup, rather than rewriting the encoder. |
| 567 base::Base64Encode(input, output); | 647 base::Base64Encode(input, output); |
| 568 for (size_t i = 0; i < output->size(); ++i) { | 648 for (size_t i = 0; i < output->size(); ++i) { |
| 569 switch (output->data()[i]) { | 649 switch (output->data()[i]) { |
| 570 case '+': | 650 case '+': |
| 571 (*output)[i] = '-'; | 651 (*output)[i] = '-'; |
| 572 continue; | 652 continue; |
| 573 case '/': | 653 case '/': |
| 574 (*output)[i] = '_'; | 654 (*output)[i] = '_'; |
| 575 continue; | 655 continue; |
| 576 default: | 656 default: |
| 577 continue; | 657 continue; |
| 578 } | 658 } |
| 579 } | 659 } |
| 580 } | 660 } |
| 581 | 661 |
| 582 } // namespace net | 662 } // namespace net |
| OLD | NEW |