| OLD | NEW |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 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/sha2.h" | 10 #include "base/sha2.h" |
| 11 #include "base/string_number_conversions.h" | 11 #include "base/string_number_conversions.h" |
| 12 #include "base/string_util.h" | 12 #include "base/string_util.h" |
| 13 #include "net/base/registry_controlled_domain.h" | 13 #include "net/base/registry_controlled_domain.h" |
| 14 #include "net/url_request/url_request_http_job.h" | 14 #include "net/url_request/url_request_http_job.h" |
| 15 | 15 |
| 16 using base::Time; | 16 using base::Time; |
| 17 using base::TimeDelta; | 17 using base::TimeDelta; |
| 18 | 18 |
| 19 //------------------------------------------------------------------------------ | 19 //------------------------------------------------------------------------------ |
| 20 // static | 20 // static |
| 21 const size_t SdchManager::kMaxDictionarySize = 1000000; | 21 const size_t SdchManager::kMaxDictionarySize = 1000000; |
| 22 | 22 |
| 23 // static | 23 // static |
| 24 const size_t SdchManager::kMaxDictionaryCount = 20; | 24 const size_t SdchManager::kMaxDictionaryCount = 20; |
| 25 | 25 |
| 26 // static | 26 // static |
| 27 SdchManager* SdchManager::global_; | 27 SdchManager* SdchManager::global_; |
| 28 | 28 |
| 29 //------------------------------------------------------------------------------ |
| 30 SdchManager::Dictionary::Dictionary(const std::string& dictionary_text, |
| 31 size_t offset, const std::string& client_hash, const GURL& gurl, |
| 32 const std::string& domain, const std::string& path, const Time& expiration, |
| 33 const std::set<int> ports) |
| 34 : text_(dictionary_text, offset), |
| 35 client_hash_(client_hash), |
| 36 url_(gurl), |
| 37 domain_(domain), |
| 38 path_(path), |
| 39 expiration_(expiration), |
| 40 ports_(ports) { |
| 41 } |
| 42 |
| 43 SdchManager::Dictionary::~Dictionary() { |
| 44 } |
| 45 |
| 46 bool SdchManager::Dictionary::CanAdvertise(const GURL& target_url) { |
| 47 if (!SdchManager::Global()->IsInSupportedDomain(target_url)) |
| 48 return false; |
| 49 /* The specific rules of when a dictionary should be advertised in an |
| 50 Avail-Dictionary header are modeled after the rules for cookie scoping. The |
| 51 terms "domain-match" and "pathmatch" are defined in RFC 2965 [6]. A |
| 52 dictionary may be advertised in the Avail-Dictionaries header exactly when |
| 53 all of the following are true: |
| 54 1. The server's effective host name domain-matches the Domain attribute of |
| 55 the dictionary. |
| 56 2. If the dictionary has a Port attribute, the request port is one of the |
| 57 ports listed in the Port attribute. |
| 58 3. The request URI path-matches the path header of the dictionary. |
| 59 4. The request is not an HTTPS request. |
| 60 */ |
| 61 if (!DomainMatch(target_url, domain_)) |
| 62 return false; |
| 63 if (!ports_.empty() && 0 == ports_.count(target_url.EffectiveIntPort())) |
| 64 return false; |
| 65 if (path_.size() && !PathMatch(target_url.path(), path_)) |
| 66 return false; |
| 67 if (target_url.SchemeIsSecure()) |
| 68 return false; |
| 69 if (Time::Now() > expiration_) |
| 70 return false; |
| 71 return true; |
| 72 } |
| 73 |
| 74 //------------------------------------------------------------------------------ |
| 75 // Security functions restricting loads and use of dictionaries. |
| 76 |
| 29 // static | 77 // static |
| 30 SdchManager* SdchManager::Global() { | 78 bool SdchManager::Dictionary::CanSet(const std::string& domain, |
| 31 return global_; | 79 const std::string& path, |
| 80 const std::set<int> ports, |
| 81 const GURL& dictionary_url) { |
| 82 if (!SdchManager::Global()->IsInSupportedDomain(dictionary_url)) |
| 83 return false; |
| 84 /* |
| 85 A dictionary is invalid and must not be stored if any of the following are |
| 86 true: |
| 87 1. The dictionary has no Domain attribute. |
| 88 2. The effective host name that derives from the referer URL host name does |
| 89 not domain-match the Domain attribute. |
| 90 3. The Domain attribute is a top level domain. |
| 91 4. The referer URL host is a host domain name (not IP address) and has the |
| 92 form HD, where D is the value of the Domain attribute, and H is a string |
| 93 that contains one or more dots. |
| 94 5. If the dictionary has a Port attribute and the referer URL's port was not |
| 95 in the list. |
| 96 */ |
| 97 |
| 98 // TODO(jar): Redirects in dictionary fetches might plausibly be problematic, |
| 99 // and hence the conservative approach is to not allow any redirects (if there |
| 100 // were any... then don't allow the dictionary to be set). |
| 101 |
| 102 if (domain.empty()) { |
| 103 SdchErrorRecovery(DICTIONARY_MISSING_DOMAIN_SPECIFIER); |
| 104 return false; // Domain is required. |
| 105 } |
| 106 if (net::RegistryControlledDomainService::GetDomainAndRegistry(domain).size() |
| 107 == 0) { |
| 108 SdchErrorRecovery(DICTIONARY_SPECIFIES_TOP_LEVEL_DOMAIN); |
| 109 return false; // domain was a TLD. |
| 110 } |
| 111 if (!Dictionary::DomainMatch(dictionary_url, domain)) { |
| 112 SdchErrorRecovery(DICTIONARY_DOMAIN_NOT_MATCHING_SOURCE_URL); |
| 113 return false; |
| 114 } |
| 115 |
| 116 std::string referrer_url_host = dictionary_url.host(); |
| 117 size_t postfix_domain_index = referrer_url_host.rfind(domain); |
| 118 // See if it is indeed a postfix, or just an internal string. |
| 119 if (referrer_url_host.size() == postfix_domain_index + domain.size()) { |
| 120 // It is a postfix... so check to see if there's a dot in the prefix. |
| 121 size_t end_of_host_index = referrer_url_host.find_first_of('.'); |
| 122 if (referrer_url_host.npos != end_of_host_index && |
| 123 end_of_host_index < postfix_domain_index) { |
| 124 SdchErrorRecovery(DICTIONARY_REFERER_URL_HAS_DOT_IN_PREFIX); |
| 125 return false; |
| 126 } |
| 127 } |
| 128 |
| 129 if (!ports.empty() |
| 130 && 0 == ports.count(dictionary_url.EffectiveIntPort())) { |
| 131 SdchErrorRecovery(DICTIONARY_PORT_NOT_MATCHING_SOURCE_URL); |
| 132 return false; |
| 133 } |
| 134 return true; |
| 32 } | 135 } |
| 33 | 136 |
| 34 // static | 137 // static |
| 35 void SdchManager::SdchErrorRecovery(ProblemCodes problem) { | 138 bool SdchManager::Dictionary::CanUse(const GURL& referring_url) { |
| 36 UMA_HISTOGRAM_ENUMERATION("Sdch3.ProblemCodes_4", problem, MAX_PROBLEM_CODE); | 139 if (!SdchManager::Global()->IsInSupportedDomain(referring_url)) |
| 140 return false; |
| 141 /* |
| 142 1. The request URL's host name domain-matches the Domain attribute of the |
| 143 dictionary. |
| 144 2. If the dictionary has a Port attribute, the request port is one of the |
| 145 ports listed in the Port attribute. |
| 146 3. The request URL path-matches the path attribute of the dictionary. |
| 147 4. The request is not an HTTPS request. |
| 148 */ |
| 149 if (!DomainMatch(referring_url, domain_)) { |
| 150 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_DOMAIN); |
| 151 return false; |
| 152 } |
| 153 if (!ports_.empty() |
| 154 && 0 == ports_.count(referring_url.EffectiveIntPort())) { |
| 155 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_PORT_LIST); |
| 156 return false; |
| 157 } |
| 158 if (path_.size() && !PathMatch(referring_url.path(), path_)) { |
| 159 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_PATH); |
| 160 return false; |
| 161 } |
| 162 if (referring_url.SchemeIsSecure()) { |
| 163 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_SCHEME); |
| 164 return false; |
| 165 } |
| 166 |
| 167 // TODO(jar): Remove overly restrictive failsafe test (added per security |
| 168 // review) when we have a need to be more general. |
| 169 if (!referring_url.SchemeIs("http")) { |
| 170 SdchErrorRecovery(ATTEMPT_TO_DECODE_NON_HTTP_DATA); |
| 171 return false; |
| 172 } |
| 173 |
| 174 return true; |
| 175 } |
| 176 |
| 177 bool SdchManager::Dictionary::PathMatch(const std::string& path, |
| 178 const std::string& restriction) { |
| 179 /* Must be either: |
| 180 1. P2 is equal to P1 |
| 181 2. P2 is a prefix of P1 and either the final character in P2 is "/" or the |
| 182 character following P2 in P1 is "/". |
| 183 */ |
| 184 if (path == restriction) |
| 185 return true; |
| 186 size_t prefix_length = restriction.size(); |
| 187 if (prefix_length > path.size()) |
| 188 return false; // Can't be a prefix. |
| 189 if (0 != path.compare(0, prefix_length, restriction)) |
| 190 return false; |
| 191 return restriction[prefix_length - 1] == '/' || path[prefix_length] == '/'; |
| 37 } | 192 } |
| 38 | 193 |
| 39 // static | 194 // static |
| 40 void SdchManager::ClearBlacklistings() { | 195 bool SdchManager::Dictionary::DomainMatch(const GURL& gurl, |
| 41 Global()->blacklisted_domains_.clear(); | 196 const std::string& restriction) { |
| 42 Global()->exponential_blacklist_count.clear(); | 197 // TODO(jar): This is not precisely a domain match definition. |
| 43 } | 198 return gurl.DomainIs(restriction.data(), restriction.size()); |
| 44 | |
| 45 // static | |
| 46 void SdchManager::ClearDomainBlacklisting(const std::string& domain) { | |
| 47 Global()->blacklisted_domains_.erase(StringToLowerASCII(domain)); | |
| 48 } | |
| 49 | |
| 50 // static | |
| 51 int SdchManager::BlackListDomainCount(const std::string& domain) { | |
| 52 if (Global()->blacklisted_domains_.end() == | |
| 53 Global()->blacklisted_domains_.find(domain)) | |
| 54 return 0; | |
| 55 return Global()->blacklisted_domains_[StringToLowerASCII(domain)]; | |
| 56 } | |
| 57 | |
| 58 // static | |
| 59 int SdchManager::BlacklistDomainExponential(const std::string& domain) { | |
| 60 if (Global()->exponential_blacklist_count.end() == | |
| 61 Global()->exponential_blacklist_count.find(domain)) | |
| 62 return 0; | |
| 63 return Global()->exponential_blacklist_count[StringToLowerASCII(domain)]; | |
| 64 } | 199 } |
| 65 | 200 |
| 66 //------------------------------------------------------------------------------ | 201 //------------------------------------------------------------------------------ |
| 67 SdchManager::SdchManager() : sdch_enabled_(false) { | 202 SdchManager::SdchManager() : sdch_enabled_(false) { |
| 68 DCHECK(!global_); | 203 DCHECK(!global_); |
| 69 global_ = this; | 204 global_ = this; |
| 70 } | 205 } |
| 71 | 206 |
| 72 SdchManager::~SdchManager() { | 207 SdchManager::~SdchManager() { |
| 73 DCHECK(global_ == this); | 208 DCHECK(global_ == this); |
| 74 while (!dictionaries_.empty()) { | 209 while (!dictionaries_.empty()) { |
| 75 DictionaryMap::iterator it = dictionaries_.begin(); | 210 DictionaryMap::iterator it = dictionaries_.begin(); |
| 76 it->second->Release(); | 211 it->second->Release(); |
| 77 dictionaries_.erase(it->first); | 212 dictionaries_.erase(it->first); |
| 78 } | 213 } |
| 79 global_ = NULL; | 214 global_ = NULL; |
| 80 } | 215 } |
| 81 | 216 |
| 82 // static | 217 // static |
| 83 void SdchManager::Shutdown() { | 218 void SdchManager::Shutdown() { |
| 84 if (!global_ ) | 219 if (!global_ ) |
| 85 return; | 220 return; |
| 86 global_->fetcher_.reset(NULL); | 221 global_->fetcher_.reset(NULL); |
| 87 } | 222 } |
| 88 | 223 |
| 89 // static | 224 // static |
| 225 SdchManager* SdchManager::Global() { |
| 226 return global_; |
| 227 } |
| 228 |
| 229 // static |
| 230 void SdchManager::SdchErrorRecovery(ProblemCodes problem) { |
| 231 UMA_HISTOGRAM_ENUMERATION("Sdch3.ProblemCodes_4", problem, MAX_PROBLEM_CODE); |
| 232 } |
| 233 |
| 234 void SdchManager::EnableSdchSupport(const std::string& domain) { |
| 235 // We presume that there is a SDCH manager instance. |
| 236 global_->supported_domain_ = domain; |
| 237 global_->sdch_enabled_ = true; |
| 238 } |
| 239 |
| 240 // static |
| 90 void SdchManager::BlacklistDomain(const GURL& url) { | 241 void SdchManager::BlacklistDomain(const GURL& url) { |
| 91 if (!global_ ) | 242 if (!global_ ) |
| 92 return; | 243 return; |
| 93 global_->SetAllowLatencyExperiment(url, false); | 244 global_->SetAllowLatencyExperiment(url, false); |
| 94 | 245 |
| 95 std::string domain(StringToLowerASCII(url.host())); | 246 std::string domain(StringToLowerASCII(url.host())); |
| 96 int count = global_->blacklisted_domains_[domain]; | 247 int count = global_->blacklisted_domains_[domain]; |
| 97 if (count > 0) | 248 if (count > 0) |
| 98 return; // Domain is already blacklisted. | 249 return; // Domain is already blacklisted. |
| 99 | 250 |
| (...skipping 10 matching lines...) Expand all Loading... |
| 110 void SdchManager::BlacklistDomainForever(const GURL& url) { | 261 void SdchManager::BlacklistDomainForever(const GURL& url) { |
| 111 if (!global_ ) | 262 if (!global_ ) |
| 112 return; | 263 return; |
| 113 global_->SetAllowLatencyExperiment(url, false); | 264 global_->SetAllowLatencyExperiment(url, false); |
| 114 | 265 |
| 115 std::string domain(StringToLowerASCII(url.host())); | 266 std::string domain(StringToLowerASCII(url.host())); |
| 116 global_->exponential_blacklist_count[domain] = INT_MAX; | 267 global_->exponential_blacklist_count[domain] = INT_MAX; |
| 117 global_->blacklisted_domains_[domain] = INT_MAX; | 268 global_->blacklisted_domains_[domain] = INT_MAX; |
| 118 } | 269 } |
| 119 | 270 |
| 120 void SdchManager::EnableSdchSupport(const std::string& domain) { | 271 // static |
| 121 // We presume that there is a SDCH manager instance. | 272 void SdchManager::ClearBlacklistings() { |
| 122 global_->supported_domain_ = domain; | 273 Global()->blacklisted_domains_.clear(); |
| 123 global_->sdch_enabled_ = true; | 274 Global()->exponential_blacklist_count.clear(); |
| 275 } |
| 276 |
| 277 // static |
| 278 void SdchManager::ClearDomainBlacklisting(const std::string& domain) { |
| 279 Global()->blacklisted_domains_.erase(StringToLowerASCII(domain)); |
| 280 } |
| 281 |
| 282 // static |
| 283 int SdchManager::BlackListDomainCount(const std::string& domain) { |
| 284 if (Global()->blacklisted_domains_.end() == |
| 285 Global()->blacklisted_domains_.find(domain)) |
| 286 return 0; |
| 287 return Global()->blacklisted_domains_[StringToLowerASCII(domain)]; |
| 288 } |
| 289 |
| 290 // static |
| 291 int SdchManager::BlacklistDomainExponential(const std::string& domain) { |
| 292 if (Global()->exponential_blacklist_count.end() == |
| 293 Global()->exponential_blacklist_count.find(domain)) |
| 294 return 0; |
| 295 return Global()->exponential_blacklist_count[StringToLowerASCII(domain)]; |
| 124 } | 296 } |
| 125 | 297 |
| 126 bool SdchManager::IsInSupportedDomain(const GURL& url) { | 298 bool SdchManager::IsInSupportedDomain(const GURL& url) { |
| 127 if (!sdch_enabled_ ) | 299 if (!sdch_enabled_ ) |
| 128 return false; | 300 return false; |
| 129 if (!supported_domain_.empty() && | 301 if (!supported_domain_.empty() && |
| 130 !url.DomainIs(supported_domain_.data(), supported_domain_.size())) | 302 !url.DomainIs(supported_domain_.data(), supported_domain_.size())) |
| 131 return false; // It is not the singular supported domain. | 303 return false; // It is not the singular supported domain. |
| 132 | 304 |
| 133 if (blacklisted_domains_.empty()) | 305 if (blacklisted_domains_.empty()) |
| 134 return true; | 306 return true; |
| 135 | 307 |
| 136 std::string domain(StringToLowerASCII(url.host())); | 308 std::string domain(StringToLowerASCII(url.host())); |
| 137 DomainCounter::iterator it = blacklisted_domains_.find(domain); | 309 DomainCounter::iterator it = blacklisted_domains_.find(domain); |
| 138 if (blacklisted_domains_.end() == it) | 310 if (blacklisted_domains_.end() == it) |
| 139 return true; | 311 return true; |
| 140 | 312 |
| 141 int count = it->second - 1; | 313 int count = it->second - 1; |
| 142 if (count > 0) | 314 if (count > 0) |
| 143 blacklisted_domains_[domain] = count; | 315 blacklisted_domains_[domain] = count; |
| 144 else | 316 else |
| 145 blacklisted_domains_.erase(domain); | 317 blacklisted_domains_.erase(domain); |
| 146 SdchErrorRecovery(DOMAIN_BLACKLIST_INCLUDES_TARGET); | 318 SdchErrorRecovery(DOMAIN_BLACKLIST_INCLUDES_TARGET); |
| 147 return false; | 319 return false; |
| 148 } | 320 } |
| 149 | 321 |
| 322 void SdchManager::FetchDictionary(const GURL& request_url, |
| 323 const GURL& dictionary_url) { |
| 324 if (SdchManager::Global()->CanFetchDictionary(request_url, dictionary_url) && |
| 325 fetcher_.get()) |
| 326 fetcher_->Schedule(dictionary_url); |
| 327 } |
| 328 |
| 150 bool SdchManager::CanFetchDictionary(const GURL& referring_url, | 329 bool SdchManager::CanFetchDictionary(const GURL& referring_url, |
| 151 const GURL& dictionary_url) const { | 330 const GURL& dictionary_url) const { |
| 152 /* The user agent may retrieve a dictionary from the dictionary URL if all of | 331 /* The user agent may retrieve a dictionary from the dictionary URL if all of |
| 153 the following are true: | 332 the following are true: |
| 154 1 The dictionary URL host name matches the referrer URL host name | 333 1 The dictionary URL host name matches the referrer URL host name |
| 155 2 The dictionary URL host name domain matches the parent domain of the | 334 2 The dictionary URL host name domain matches the parent domain of the |
| 156 referrer URL host name | 335 referrer URL host name |
| 157 3 The parent domain of the referrer URL host name is not a top level | 336 3 The parent domain of the referrer URL host name is not a top level |
| 158 domain | 337 domain |
| 159 4 The dictionary URL is not an HTTPS URL. | 338 4 The dictionary URL is not an HTTPS URL. |
| (...skipping 12 matching lines...) Expand all Loading... |
| 172 // TODO(jar): Remove this failsafe conservative hack which is more restrictive | 351 // TODO(jar): Remove this failsafe conservative hack which is more restrictive |
| 173 // than current SDCH spec when needed, and justified by security audit. | 352 // than current SDCH spec when needed, and justified by security audit. |
| 174 if (!referring_url.SchemeIs("http")) { | 353 if (!referring_url.SchemeIs("http")) { |
| 175 SdchErrorRecovery(DICTIONARY_SELECTED_FROM_NON_HTTP); | 354 SdchErrorRecovery(DICTIONARY_SELECTED_FROM_NON_HTTP); |
| 176 return false; | 355 return false; |
| 177 } | 356 } |
| 178 | 357 |
| 179 return true; | 358 return true; |
| 180 } | 359 } |
| 181 | 360 |
| 182 void SdchManager::FetchDictionary(const GURL& request_url, | |
| 183 const GURL& dictionary_url) { | |
| 184 if (SdchManager::Global()->CanFetchDictionary(request_url, dictionary_url) && | |
| 185 fetcher_.get()) | |
| 186 fetcher_->Schedule(dictionary_url); | |
| 187 } | |
| 188 | |
| 189 bool SdchManager::AddSdchDictionary(const std::string& dictionary_text, | 361 bool SdchManager::AddSdchDictionary(const std::string& dictionary_text, |
| 190 const GURL& dictionary_url) { | 362 const GURL& dictionary_url) { |
| 191 std::string client_hash; | 363 std::string client_hash; |
| 192 std::string server_hash; | 364 std::string server_hash; |
| 193 GenerateHash(dictionary_text, &client_hash, &server_hash); | 365 GenerateHash(dictionary_text, &client_hash, &server_hash); |
| 194 if (dictionaries_.find(server_hash) != dictionaries_.end()) { | 366 if (dictionaries_.find(server_hash) != dictionaries_.end()) { |
| 195 SdchErrorRecovery(DICTIONARY_ALREADY_LOADED); | 367 SdchErrorRecovery(DICTIONARY_ALREADY_LOADED); |
| 196 return false; // Already loaded. | 368 return false; // Already loaded. |
| 197 } | 369 } |
| 198 | 370 |
| (...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 310 ++count; | 482 ++count; |
| 311 if (!list->empty()) | 483 if (!list->empty()) |
| 312 list->append(","); | 484 list->append(","); |
| 313 list->append(it->second->client_hash()); | 485 list->append(it->second->client_hash()); |
| 314 } | 486 } |
| 315 // Watch to see if we have corrupt or numerous dictionaries. | 487 // Watch to see if we have corrupt or numerous dictionaries. |
| 316 if (count > 0) | 488 if (count > 0) |
| 317 UMA_HISTOGRAM_COUNTS("Sdch3.Advertisement_Count", count); | 489 UMA_HISTOGRAM_COUNTS("Sdch3.Advertisement_Count", count); |
| 318 } | 490 } |
| 319 | 491 |
| 320 SdchManager::Dictionary::Dictionary(const std::string& dictionary_text, | |
| 321 size_t offset, const std::string& client_hash, const GURL& gurl, | |
| 322 const std::string& domain, const std::string& path, const Time& expiration, | |
| 323 const std::set<int> ports) | |
| 324 : text_(dictionary_text, offset), | |
| 325 client_hash_(client_hash), | |
| 326 url_(gurl), | |
| 327 domain_(domain), | |
| 328 path_(path), | |
| 329 expiration_(expiration), | |
| 330 ports_(ports) { | |
| 331 } | |
| 332 | |
| 333 SdchManager::Dictionary::~Dictionary() { | |
| 334 } | |
| 335 | |
| 336 // static | 492 // static |
| 337 void SdchManager::GenerateHash(const std::string& dictionary_text, | 493 void SdchManager::GenerateHash(const std::string& dictionary_text, |
| 338 std::string* client_hash, std::string* server_hash) { | 494 std::string* client_hash, std::string* server_hash) { |
| 339 char binary_hash[32]; | 495 char binary_hash[32]; |
| 340 base::SHA256HashString(dictionary_text, binary_hash, sizeof(binary_hash)); | 496 base::SHA256HashString(dictionary_text, binary_hash, sizeof(binary_hash)); |
| 341 | 497 |
| 342 std::string first_48_bits(&binary_hash[0], 6); | 498 std::string first_48_bits(&binary_hash[0], 6); |
| 343 std::string second_48_bits(&binary_hash[6], 6); | 499 std::string second_48_bits(&binary_hash[6], 6); |
| 344 UrlSafeBase64Encode(first_48_bits, client_hash); | 500 UrlSafeBase64Encode(first_48_bits, client_hash); |
| 345 UrlSafeBase64Encode(second_48_bits, server_hash); | 501 UrlSafeBase64Encode(second_48_bits, server_hash); |
| 346 | 502 |
| 347 DCHECK_EQ(server_hash->length(), 8u); | 503 DCHECK_EQ(server_hash->length(), 8u); |
| 348 DCHECK_EQ(client_hash->length(), 8u); | 504 DCHECK_EQ(client_hash->length(), 8u); |
| 349 } | 505 } |
| 350 | 506 |
| 351 // static | |
| 352 void SdchManager::UrlSafeBase64Encode(const std::string& input, | |
| 353 std::string* output) { | |
| 354 // Since this is only done during a dictionary load, and hashes are only 8 | |
| 355 // characters, we just do the simple fixup, rather than rewriting the encoder. | |
| 356 base::Base64Encode(input, output); | |
| 357 for (size_t i = 0; i < output->size(); ++i) { | |
| 358 switch (output->data()[i]) { | |
| 359 case '+': | |
| 360 (*output)[i] = '-'; | |
| 361 continue; | |
| 362 case '/': | |
| 363 (*output)[i] = '_'; | |
| 364 continue; | |
| 365 default: | |
| 366 continue; | |
| 367 } | |
| 368 } | |
| 369 } | |
| 370 | |
| 371 //------------------------------------------------------------------------------ | |
| 372 // Security functions restricting loads and use of dictionaries. | |
| 373 | |
| 374 // static | |
| 375 bool SdchManager::Dictionary::CanSet(const std::string& domain, | |
| 376 const std::string& path, | |
| 377 const std::set<int> ports, | |
| 378 const GURL& dictionary_url) { | |
| 379 if (!SdchManager::Global()->IsInSupportedDomain(dictionary_url)) | |
| 380 return false; | |
| 381 /* | |
| 382 A dictionary is invalid and must not be stored if any of the following are | |
| 383 true: | |
| 384 1. The dictionary has no Domain attribute. | |
| 385 2. The effective host name that derives from the referer URL host name does | |
| 386 not domain-match the Domain attribute. | |
| 387 3. The Domain attribute is a top level domain. | |
| 388 4. The referer URL host is a host domain name (not IP address) and has the | |
| 389 form HD, where D is the value of the Domain attribute, and H is a string | |
| 390 that contains one or more dots. | |
| 391 5. If the dictionary has a Port attribute and the referer URL's port was not | |
| 392 in the list. | |
| 393 */ | |
| 394 | |
| 395 // TODO(jar): Redirects in dictionary fetches might plausibly be problematic, | |
| 396 // and hence the conservative approach is to not allow any redirects (if there | |
| 397 // were any... then don't allow the dictionary to be set). | |
| 398 | |
| 399 if (domain.empty()) { | |
| 400 SdchErrorRecovery(DICTIONARY_MISSING_DOMAIN_SPECIFIER); | |
| 401 return false; // Domain is required. | |
| 402 } | |
| 403 if (net::RegistryControlledDomainService::GetDomainAndRegistry(domain).size() | |
| 404 == 0) { | |
| 405 SdchErrorRecovery(DICTIONARY_SPECIFIES_TOP_LEVEL_DOMAIN); | |
| 406 return false; // domain was a TLD. | |
| 407 } | |
| 408 if (!Dictionary::DomainMatch(dictionary_url, domain)) { | |
| 409 SdchErrorRecovery(DICTIONARY_DOMAIN_NOT_MATCHING_SOURCE_URL); | |
| 410 return false; | |
| 411 } | |
| 412 | |
| 413 std::string referrer_url_host = dictionary_url.host(); | |
| 414 size_t postfix_domain_index = referrer_url_host.rfind(domain); | |
| 415 // See if it is indeed a postfix, or just an internal string. | |
| 416 if (referrer_url_host.size() == postfix_domain_index + domain.size()) { | |
| 417 // It is a postfix... so check to see if there's a dot in the prefix. | |
| 418 size_t end_of_host_index = referrer_url_host.find_first_of('.'); | |
| 419 if (referrer_url_host.npos != end_of_host_index && | |
| 420 end_of_host_index < postfix_domain_index) { | |
| 421 SdchErrorRecovery(DICTIONARY_REFERER_URL_HAS_DOT_IN_PREFIX); | |
| 422 return false; | |
| 423 } | |
| 424 } | |
| 425 | |
| 426 if (!ports.empty() | |
| 427 && 0 == ports.count(dictionary_url.EffectiveIntPort())) { | |
| 428 SdchErrorRecovery(DICTIONARY_PORT_NOT_MATCHING_SOURCE_URL); | |
| 429 return false; | |
| 430 } | |
| 431 return true; | |
| 432 } | |
| 433 | |
| 434 // static | |
| 435 bool SdchManager::Dictionary::CanUse(const GURL& referring_url) { | |
| 436 if (!SdchManager::Global()->IsInSupportedDomain(referring_url)) | |
| 437 return false; | |
| 438 /* | |
| 439 1. The request URL's host name domain-matches the Domain attribute of the | |
| 440 dictionary. | |
| 441 2. If the dictionary has a Port attribute, the request port is one of the | |
| 442 ports listed in the Port attribute. | |
| 443 3. The request URL path-matches the path attribute of the dictionary. | |
| 444 4. The request is not an HTTPS request. | |
| 445 */ | |
| 446 if (!DomainMatch(referring_url, domain_)) { | |
| 447 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_DOMAIN); | |
| 448 return false; | |
| 449 } | |
| 450 if (!ports_.empty() | |
| 451 && 0 == ports_.count(referring_url.EffectiveIntPort())) { | |
| 452 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_PORT_LIST); | |
| 453 return false; | |
| 454 } | |
| 455 if (path_.size() && !PathMatch(referring_url.path(), path_)) { | |
| 456 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_PATH); | |
| 457 return false; | |
| 458 } | |
| 459 if (referring_url.SchemeIsSecure()) { | |
| 460 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_SCHEME); | |
| 461 return false; | |
| 462 } | |
| 463 | |
| 464 // TODO(jar): Remove overly restrictive failsafe test (added per security | |
| 465 // review) when we have a need to be more general. | |
| 466 if (!referring_url.SchemeIs("http")) { | |
| 467 SdchErrorRecovery(ATTEMPT_TO_DECODE_NON_HTTP_DATA); | |
| 468 return false; | |
| 469 } | |
| 470 | |
| 471 return true; | |
| 472 } | |
| 473 | |
| 474 bool SdchManager::Dictionary::CanAdvertise(const GURL& target_url) { | |
| 475 if (!SdchManager::Global()->IsInSupportedDomain(target_url)) | |
| 476 return false; | |
| 477 /* The specific rules of when a dictionary should be advertised in an | |
| 478 Avail-Dictionary header are modeled after the rules for cookie scoping. The | |
| 479 terms "domain-match" and "pathmatch" are defined in RFC 2965 [6]. A | |
| 480 dictionary may be advertised in the Avail-Dictionaries header exactly when | |
| 481 all of the following are true: | |
| 482 1. The server's effective host name domain-matches the Domain attribute of | |
| 483 the dictionary. | |
| 484 2. If the dictionary has a Port attribute, the request port is one of the | |
| 485 ports listed in the Port attribute. | |
| 486 3. The request URI path-matches the path header of the dictionary. | |
| 487 4. The request is not an HTTPS request. | |
| 488 */ | |
| 489 if (!DomainMatch(target_url, domain_)) | |
| 490 return false; | |
| 491 if (!ports_.empty() && 0 == ports_.count(target_url.EffectiveIntPort())) | |
| 492 return false; | |
| 493 if (path_.size() && !PathMatch(target_url.path(), path_)) | |
| 494 return false; | |
| 495 if (target_url.SchemeIsSecure()) | |
| 496 return false; | |
| 497 if (Time::Now() > expiration_) | |
| 498 return false; | |
| 499 return true; | |
| 500 } | |
| 501 | |
| 502 bool SdchManager::Dictionary::PathMatch(const std::string& path, | |
| 503 const std::string& restriction) { | |
| 504 /* Must be either: | |
| 505 1. P2 is equal to P1 | |
| 506 2. P2 is a prefix of P1 and either the final character in P2 is "/" or the | |
| 507 character following P2 in P1 is "/". | |
| 508 */ | |
| 509 if (path == restriction) | |
| 510 return true; | |
| 511 size_t prefix_length = restriction.size(); | |
| 512 if (prefix_length > path.size()) | |
| 513 return false; // Can't be a prefix. | |
| 514 if (0 != path.compare(0, prefix_length, restriction)) | |
| 515 return false; | |
| 516 return restriction[prefix_length - 1] == '/' || path[prefix_length] == '/'; | |
| 517 } | |
| 518 | |
| 519 // static | |
| 520 bool SdchManager::Dictionary::DomainMatch(const GURL& gurl, | |
| 521 const std::string& restriction) { | |
| 522 // TODO(jar): This is not precisely a domain match definition. | |
| 523 return gurl.DomainIs(restriction.data(), restriction.size()); | |
| 524 } | |
| 525 | |
| 526 //------------------------------------------------------------------------------ | 507 //------------------------------------------------------------------------------ |
| 527 // Methods for supporting latency experiments. | 508 // Methods for supporting latency experiments. |
| 528 | 509 |
| 529 bool SdchManager::AllowLatencyExperiment(const GURL& url) const { | 510 bool SdchManager::AllowLatencyExperiment(const GURL& url) const { |
| 530 return allow_latency_experiment_.end() != | 511 return allow_latency_experiment_.end() != |
| 531 allow_latency_experiment_.find(url.host()); | 512 allow_latency_experiment_.find(url.host()); |
| 532 } | 513 } |
| 533 | 514 |
| 534 void SdchManager::SetAllowLatencyExperiment(const GURL& url, bool enable) { | 515 void SdchManager::SetAllowLatencyExperiment(const GURL& url, bool enable) { |
| 535 if (enable) { | 516 if (enable) { |
| 536 allow_latency_experiment_.insert(url.host()); | 517 allow_latency_experiment_.insert(url.host()); |
| 537 return; | 518 return; |
| 538 } | 519 } |
| 539 ExperimentSet::iterator it = allow_latency_experiment_.find(url.host()); | 520 ExperimentSet::iterator it = allow_latency_experiment_.find(url.host()); |
| 540 if (allow_latency_experiment_.end() == it) | 521 if (allow_latency_experiment_.end() == it) |
| 541 return; // It was already erased, or never allowed. | 522 return; // It was already erased, or never allowed. |
| 542 SdchErrorRecovery(LATENCY_TEST_DISALLOWED); | 523 SdchErrorRecovery(LATENCY_TEST_DISALLOWED); |
| 543 allow_latency_experiment_.erase(it); | 524 allow_latency_experiment_.erase(it); |
| 544 } | 525 } |
| 526 |
| 527 // static |
| 528 void SdchManager::UrlSafeBase64Encode(const std::string& input, |
| 529 std::string* output) { |
| 530 // Since this is only done during a dictionary load, and hashes are only 8 |
| 531 // characters, we just do the simple fixup, rather than rewriting the encoder. |
| 532 base::Base64Encode(input, output); |
| 533 for (size_t i = 0; i < output->size(); ++i) { |
| 534 switch (output->data()[i]) { |
| 535 case '+': |
| 536 (*output)[i] = '-'; |
| 537 continue; |
| 538 case '/': |
| 539 (*output)[i] = '_'; |
| 540 continue; |
| 541 default: |
| 542 continue; |
| 543 } |
| 544 } |
| 545 } |
| OLD | NEW |