| 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 { | 17 namespace { |
| 17 | 18 |
| 18 void StripTrailingDot(GURL* gurl) { | 19 void StripTrailingDot(GURL* gurl) { |
| 19 std::string host(gurl->host()); | 20 std::string host(gurl->host()); |
| 20 | 21 |
| 21 if (host.empty()) | 22 if (host.empty()) |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 75 url_(gurl), | 76 url_(gurl), |
| 76 domain_(domain), | 77 domain_(domain), |
| 77 path_(path), | 78 path_(path), |
| 78 expiration_(expiration), | 79 expiration_(expiration), |
| 79 ports_(ports) { | 80 ports_(ports) { |
| 80 } | 81 } |
| 81 | 82 |
| 82 SdchManager::Dictionary::~Dictionary() { | 83 SdchManager::Dictionary::~Dictionary() { |
| 83 } | 84 } |
| 84 | 85 |
| 85 bool SdchManager::Dictionary::CanAdvertise(const GURL& target_url) { | 86 SdchProblemCode SdchManager::Dictionary::CanAdvertise( |
| 87 const GURL& target_url) const { |
| 86 /* The specific rules of when a dictionary should be advertised in an | 88 /* The specific rules of when a dictionary should be advertised in an |
| 87 Avail-Dictionary header are modeled after the rules for cookie scoping. The | 89 Avail-Dictionary header are modeled after the rules for cookie scoping. The |
| 88 terms "domain-match" and "pathmatch" are defined in RFC 2965 [6]. A | 90 terms "domain-match" and "pathmatch" are defined in RFC 2965 [6]. A |
| 89 dictionary may be advertised in the Avail-Dictionaries header exactly when | 91 dictionary may be advertised in the Avail-Dictionaries header exactly when |
| 90 all of the following are true: | 92 all of the following are true: |
| 91 1. The server's effective host name domain-matches the Domain attribute of | 93 1. The server's effective host name domain-matches the Domain attribute of |
| 92 the dictionary. | 94 the dictionary. |
| 93 2. If the dictionary has a Port attribute, the request port is one of the | 95 2. If the dictionary has a Port attribute, the request port is one of the |
| 94 ports listed in the Port attribute. | 96 ports listed in the Port attribute. |
| 95 3. The request URI path-matches the path header of the dictionary. | 97 3. The request URI path-matches the path header of the dictionary. |
| 96 4. The request is not an HTTPS request. | 98 4. The request is not an HTTPS request. |
| 97 We can override (ignore) item (4) only when we have explicitly enabled | 99 We can override (ignore) item (4) only when we have explicitly enabled |
| 98 HTTPS support AND the dictionary acquisition scheme matches the target | 100 HTTPS support AND the dictionary acquisition scheme matches the target |
| 99 url scheme. | 101 url scheme. |
| 100 */ | 102 */ |
| 101 if (!DomainMatch(target_url, domain_)) | 103 if (!DomainMatch(target_url, domain_)) |
| 102 return false; | 104 return SDCH_DICTIONARY_FOUND_HAS_WRONG_DOMAIN; |
| 103 if (!ports_.empty() && 0 == ports_.count(target_url.EffectiveIntPort())) | 105 if (!ports_.empty() && 0 == ports_.count(target_url.EffectiveIntPort())) |
| 104 return false; | 106 return SDCH_DICTIONARY_FOUND_HAS_WRONG_PORT_LIST; |
| 105 if (path_.size() && !PathMatch(target_url.path(), path_)) | 107 if (path_.size() && !PathMatch(target_url.path(), path_)) |
| 106 return false; | 108 return SDCH_DICTIONARY_FOUND_HAS_WRONG_PATH; |
| 107 if (!SdchManager::secure_scheme_supported() && target_url.SchemeIsSecure()) | 109 if (!SdchManager::secure_scheme_supported() && target_url.SchemeIsSecure()) |
| 108 return false; | 110 return SDCH_DICTIONARY_FOUND_HAS_WRONG_SCHEME; |
| 109 if (target_url.SchemeIsSecure() != url_.SchemeIsSecure()) | 111 if (target_url.SchemeIsSecure() != url_.SchemeIsSecure()) |
| 110 return false; | 112 return SDCH_DICTIONARY_FOUND_HAS_WRONG_SCHEME; |
| 111 if (base::Time::Now() > expiration_) | 113 if (base::Time::Now() > expiration_) |
| 112 return false; | 114 return SDCH_DICTIONARY_FOUND_EXPIRED; |
| 113 return true; | 115 return SDCH_OK; |
| 114 } | 116 } |
| 115 | 117 |
| 116 //------------------------------------------------------------------------------ | 118 //------------------------------------------------------------------------------ |
| 117 // Security functions restricting loads and use of dictionaries. | 119 // Security functions restricting loads and use of dictionaries. |
| 118 | 120 |
| 119 // static | 121 // static |
| 120 bool SdchManager::Dictionary::CanSet(const std::string& domain, | 122 SdchProblemCode SdchManager::Dictionary::CanSet(const std::string& domain, |
| 121 const std::string& path, | 123 const std::string& path, |
| 122 const std::set<int>& ports, | 124 const std::set<int>& ports, |
| 123 const GURL& dictionary_url) { | 125 const GURL& dictionary_url) { |
| 124 /* | 126 /* |
| 125 A dictionary is invalid and must not be stored if any of the following are | 127 A dictionary is invalid and must not be stored if any of the following are |
| 126 true: | 128 true: |
| 127 1. The dictionary has no Domain attribute. | 129 1. The dictionary has no Domain attribute. |
| 128 2. The effective host name that derives from the referer URL host name does | 130 2. The effective host name that derives from the referer URL host name does |
| 129 not domain-match the Domain attribute. | 131 not domain-match the Domain attribute. |
| 130 3. The Domain attribute is a top level domain. | 132 3. The Domain attribute is a top level domain. |
| 131 4. The referer URL host is a host domain name (not IP address) and has the | 133 4. The referer URL host is a host domain name (not IP address) and has the |
| 132 form HD, where D is the value of the Domain attribute, and H is a string | 134 form HD, where D is the value of the Domain attribute, and H is a string |
| 133 that contains one or more dots. | 135 that contains one or more dots. |
| 134 5. If the dictionary has a Port attribute and the referer URL's port was not | 136 5. If the dictionary has a Port attribute and the referer URL's port was not |
| 135 in the list. | 137 in the list. |
| 136 */ | 138 */ |
| 137 | 139 |
| 138 // TODO(jar): Redirects in dictionary fetches might plausibly be problematic, | 140 // TODO(jar): Redirects in dictionary fetches might plausibly be problematic, |
| 139 // and hence the conservative approach is to not allow any redirects (if there | 141 // and hence the conservative approach is to not allow any redirects (if there |
| 140 // were any... then don't allow the dictionary to be set). | 142 // were any... then don't allow the dictionary to be set). |
| 141 | 143 |
| 142 if (domain.empty()) { | 144 if (domain.empty()) |
| 143 SdchErrorRecovery(DICTIONARY_MISSING_DOMAIN_SPECIFIER); | 145 return SDCH_DICTIONARY_MISSING_DOMAIN_SPECIFIER; // Domain is required. |
| 144 return false; // Domain is required. | 146 |
| 147 if (registry_controlled_domains::GetDomainAndRegistry( |
| 148 domain, registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES) |
| 149 .empty()) { |
| 150 return SDCH_DICTIONARY_SPECIFIES_TOP_LEVEL_DOMAIN; // domain was a TLD. |
| 145 } | 151 } |
| 146 if (registry_controlled_domains::GetDomainAndRegistry( | 152 |
| 147 domain, | 153 if (!Dictionary::DomainMatch(dictionary_url, domain)) |
| 148 registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES).empty()) { | 154 return SDCH_DICTIONARY_DOMAIN_NOT_MATCHING_SOURCE_URL; |
| 149 SdchErrorRecovery(DICTIONARY_SPECIFIES_TOP_LEVEL_DOMAIN); | |
| 150 return false; // domain was a TLD. | |
| 151 } | |
| 152 if (!Dictionary::DomainMatch(dictionary_url, domain)) { | |
| 153 SdchErrorRecovery(DICTIONARY_DOMAIN_NOT_MATCHING_SOURCE_URL); | |
| 154 return false; | |
| 155 } | |
| 156 | 155 |
| 157 std::string referrer_url_host = dictionary_url.host(); | 156 std::string referrer_url_host = dictionary_url.host(); |
| 158 size_t postfix_domain_index = referrer_url_host.rfind(domain); | 157 size_t postfix_domain_index = referrer_url_host.rfind(domain); |
| 159 // See if it is indeed a postfix, or just an internal string. | 158 // See if it is indeed a postfix, or just an internal string. |
| 160 if (referrer_url_host.size() == postfix_domain_index + domain.size()) { | 159 if (referrer_url_host.size() == postfix_domain_index + domain.size()) { |
| 161 // It is a postfix... so check to see if there's a dot in the prefix. | 160 // It is a postfix... so check to see if there's a dot in the prefix. |
| 162 size_t end_of_host_index = referrer_url_host.find_first_of('.'); | 161 size_t end_of_host_index = referrer_url_host.find_first_of('.'); |
| 163 if (referrer_url_host.npos != end_of_host_index && | 162 if (referrer_url_host.npos != end_of_host_index && |
| 164 end_of_host_index < postfix_domain_index) { | 163 end_of_host_index < postfix_domain_index) { |
| 165 SdchErrorRecovery(DICTIONARY_REFERER_URL_HAS_DOT_IN_PREFIX); | 164 return SDCH_DICTIONARY_REFERER_URL_HAS_DOT_IN_PREFIX; |
| 166 return false; | |
| 167 } | 165 } |
| 168 } | 166 } |
| 169 | 167 |
| 170 if (!ports.empty() | 168 if (!ports.empty() && 0 == ports.count(dictionary_url.EffectiveIntPort())) |
| 171 && 0 == ports.count(dictionary_url.EffectiveIntPort())) { | 169 return SDCH_DICTIONARY_PORT_NOT_MATCHING_SOURCE_URL; |
| 172 SdchErrorRecovery(DICTIONARY_PORT_NOT_MATCHING_SOURCE_URL); | 170 |
| 173 return false; | 171 return SDCH_OK; |
| 174 } | |
| 175 return true; | |
| 176 } | 172 } |
| 177 | 173 |
| 178 // static | 174 SdchProblemCode SdchManager::Dictionary::CanUse( |
| 179 bool SdchManager::Dictionary::CanUse(const GURL& referring_url) { | 175 const GURL& referring_url) const { |
| 180 /* | 176 /* |
| 181 1. The request URL's host name domain-matches the Domain attribute of the | 177 1. The request URL's host name domain-matches the Domain attribute of the |
| 182 dictionary. | 178 dictionary. |
| 183 2. If the dictionary has a Port attribute, the request port is one of the | 179 2. If the dictionary has a Port attribute, the request port is one of the |
| 184 ports listed in the Port attribute. | 180 ports listed in the Port attribute. |
| 185 3. The request URL path-matches the path attribute of the dictionary. | 181 3. The request URL path-matches the path attribute of the dictionary. |
| 186 4. The request is not an HTTPS request. | 182 4. The request is not an HTTPS request. |
| 187 We can override (ignore) item (4) only when we have explicitly enabled | 183 We can override (ignore) item (4) only when we have explicitly enabled |
| 188 HTTPS support AND the dictionary acquisition scheme matches the target | 184 HTTPS support AND the dictionary acquisition scheme matches the target |
| 189 url scheme. | 185 url scheme. |
| 190 */ | 186 */ |
| 191 if (!DomainMatch(referring_url, domain_)) { | 187 if (!DomainMatch(referring_url, domain_)) |
| 192 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_DOMAIN); | 188 return SDCH_DICTIONARY_FOUND_HAS_WRONG_DOMAIN; |
| 193 return false; | 189 |
| 194 } | 190 if (!ports_.empty() && 0 == ports_.count(referring_url.EffectiveIntPort())) |
| 195 if (!ports_.empty() | 191 return SDCH_DICTIONARY_FOUND_HAS_WRONG_PORT_LIST; |
| 196 && 0 == ports_.count(referring_url.EffectiveIntPort())) { | 192 |
| 197 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_PORT_LIST); | 193 if (path_.size() && !PathMatch(referring_url.path(), path_)) |
| 198 return false; | 194 return SDCH_DICTIONARY_FOUND_HAS_WRONG_PATH; |
| 199 } | 195 |
| 200 if (path_.size() && !PathMatch(referring_url.path(), path_)) { | 196 if (!SdchManager::secure_scheme_supported() && referring_url.SchemeIsSecure()) |
| 201 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_PATH); | 197 return SDCH_DICTIONARY_FOUND_HAS_WRONG_SCHEME; |
| 202 return false; | 198 |
| 203 } | 199 if (referring_url.SchemeIsSecure() != url_.SchemeIsSecure()) |
| 204 if (!SdchManager::secure_scheme_supported() && | 200 return SDCH_DICTIONARY_FOUND_HAS_WRONG_SCHEME; |
| 205 referring_url.SchemeIsSecure()) { | |
| 206 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_SCHEME); | |
| 207 return false; | |
| 208 } | |
| 209 if (referring_url.SchemeIsSecure() != url_.SchemeIsSecure()) { | |
| 210 SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_SCHEME); | |
| 211 return false; | |
| 212 } | |
| 213 | 201 |
| 214 // TODO(jar): Remove overly restrictive failsafe test (added per security | 202 // TODO(jar): Remove overly restrictive failsafe test (added per security |
| 215 // review) when we have a need to be more general. | 203 // review) when we have a need to be more general. |
| 216 if (!referring_url.SchemeIsHTTPOrHTTPS()) { | 204 if (!referring_url.SchemeIsHTTPOrHTTPS()) |
| 217 SdchErrorRecovery(ATTEMPT_TO_DECODE_NON_HTTP_DATA); | 205 return SDCH_ATTEMPT_TO_DECODE_NON_HTTP_DATA; |
| 218 return false; | |
| 219 } | |
| 220 | 206 |
| 221 return true; | 207 return SDCH_OK; |
| 222 } | 208 } |
| 223 | 209 |
| 210 // static |
| 224 bool SdchManager::Dictionary::PathMatch(const std::string& path, | 211 bool SdchManager::Dictionary::PathMatch(const std::string& path, |
| 225 const std::string& restriction) { | 212 const std::string& restriction) { |
| 226 /* Must be either: | 213 /* Must be either: |
| 227 1. P2 is equal to P1 | 214 1. P2 is equal to P1 |
| 228 2. P2 is a prefix of P1 and either the final character in P2 is "/" or the | 215 2. P2 is a prefix of P1 and either the final character in P2 is "/" or the |
| 229 character following P2 in P1 is "/". | 216 character following P2 in P1 is "/". |
| 230 */ | 217 */ |
| 231 if (path == restriction) | 218 if (path == restriction) |
| 232 return true; | 219 return true; |
| 233 size_t prefix_length = restriction.size(); | 220 size_t prefix_length = restriction.size(); |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 266 fetcher_->Cancel(); | 253 fetcher_->Cancel(); |
| 267 | 254 |
| 268 // Note that this may result in not having dictionaries we've advertised | 255 // Note that this may result in not having dictionaries we've advertised |
| 269 // for incoming responses. The window is relatively small (as ClearData() | 256 // for incoming responses. The window is relatively small (as ClearData() |
| 270 // is not expected to be called frequently), so we rely on meta-refresh | 257 // is not expected to be called frequently), so we rely on meta-refresh |
| 271 // to handle this case. | 258 // to handle this case. |
| 272 dictionaries_.clear(); | 259 dictionaries_.clear(); |
| 273 } | 260 } |
| 274 | 261 |
| 275 // static | 262 // static |
| 276 void SdchManager::SdchErrorRecovery(ProblemCodes problem) { | 263 void SdchManager::SdchErrorRecovery(SdchProblemCode problem) { |
| 277 UMA_HISTOGRAM_ENUMERATION("Sdch3.ProblemCodes_4", problem, MAX_PROBLEM_CODE); | 264 UMA_HISTOGRAM_ENUMERATION( |
| 265 "Sdch3.ProblemCodes_5", problem, SDCH_MAX_PROBLEM_CODE); |
| 278 } | 266 } |
| 279 | 267 |
| 280 void SdchManager::set_sdch_fetcher(scoped_ptr<SdchFetcher> fetcher) { | 268 void SdchManager::set_sdch_fetcher(scoped_ptr<SdchFetcher> fetcher) { |
| 281 DCHECK(CalledOnValidThread()); | 269 DCHECK(CalledOnValidThread()); |
| 282 fetcher_ = fetcher.Pass(); | 270 fetcher_ = fetcher.Pass(); |
| 283 } | 271 } |
| 284 | 272 |
| 285 // static | 273 // static |
| 286 void SdchManager::EnableSdchSupport(bool enabled) { | 274 void SdchManager::EnableSdchSupport(bool enabled) { |
| 287 g_sdch_enabled_ = enabled; | 275 g_sdch_enabled_ = enabled; |
| 288 } | 276 } |
| 289 | 277 |
| 290 // static | 278 // static |
| 291 void SdchManager::EnableSecureSchemeSupport(bool enabled) { | 279 void SdchManager::EnableSecureSchemeSupport(bool enabled) { |
| 292 g_secure_scheme_supported_ = enabled; | 280 g_secure_scheme_supported_ = enabled; |
| 293 } | 281 } |
| 294 | 282 |
| 295 void SdchManager::BlacklistDomain(const GURL& url, | 283 void SdchManager::BlacklistDomain(const GURL& url, |
| 296 ProblemCodes blacklist_reason) { | 284 SdchProblemCode blacklist_reason) { |
| 297 SetAllowLatencyExperiment(url, false); | 285 SetAllowLatencyExperiment(url, false); |
| 298 | 286 |
| 299 BlacklistInfo* blacklist_info = | 287 BlacklistInfo* blacklist_info = |
| 300 &blacklisted_domains_[base::StringToLowerASCII(url.host())]; | 288 &blacklisted_domains_[base::StringToLowerASCII(url.host())]; |
| 301 | 289 |
| 302 if (blacklist_info->count > 0) | 290 if (blacklist_info->count > 0) |
| 303 return; // Domain is already blacklisted. | 291 return; // Domain is already blacklisted. |
| 304 | 292 |
| 305 if (blacklist_info->exponential_count > (INT_MAX - 1) / 2) { | 293 if (blacklist_info->exponential_count > (INT_MAX - 1) / 2) { |
| 306 blacklist_info->exponential_count = INT_MAX; | 294 blacklist_info->exponential_count = INT_MAX; |
| 307 } else { | 295 } else { |
| 308 blacklist_info->exponential_count = | 296 blacklist_info->exponential_count = |
| 309 blacklist_info->exponential_count * 2 + 1; | 297 blacklist_info->exponential_count * 2 + 1; |
| 310 } | 298 } |
| 311 | 299 |
| 312 blacklist_info->count = blacklist_info->exponential_count; | 300 blacklist_info->count = blacklist_info->exponential_count; |
| 313 blacklist_info->reason = blacklist_reason; | 301 blacklist_info->reason = blacklist_reason; |
| 314 } | 302 } |
| 315 | 303 |
| 316 void SdchManager::BlacklistDomainForever(const GURL& url, | 304 void SdchManager::BlacklistDomainForever(const GURL& url, |
| 317 ProblemCodes blacklist_reason) { | 305 SdchProblemCode blacklist_reason) { |
| 318 SetAllowLatencyExperiment(url, false); | 306 SetAllowLatencyExperiment(url, false); |
| 319 | 307 |
| 320 BlacklistInfo* blacklist_info = | 308 BlacklistInfo* blacklist_info = |
| 321 &blacklisted_domains_[base::StringToLowerASCII(url.host())]; | 309 &blacklisted_domains_[base::StringToLowerASCII(url.host())]; |
| 322 blacklist_info->count = INT_MAX; | 310 blacklist_info->count = INT_MAX; |
| 323 blacklist_info->exponential_count = INT_MAX; | 311 blacklist_info->exponential_count = INT_MAX; |
| 324 blacklist_info->reason = blacklist_reason; | 312 blacklist_info->reason = blacklist_reason; |
| 325 } | 313 } |
| 326 | 314 |
| 327 void SdchManager::ClearBlacklistings() { | 315 void SdchManager::ClearBlacklistings() { |
| 328 blacklisted_domains_.clear(); | 316 blacklisted_domains_.clear(); |
| 329 } | 317 } |
| 330 | 318 |
| 331 void SdchManager::ClearDomainBlacklisting(const std::string& domain) { | 319 void SdchManager::ClearDomainBlacklisting(const std::string& domain) { |
| 332 BlacklistInfo* blacklist_info = &blacklisted_domains_[ | 320 BlacklistInfo* blacklist_info = &blacklisted_domains_[ |
| 333 base::StringToLowerASCII(domain)]; | 321 base::StringToLowerASCII(domain)]; |
| 334 blacklist_info->count = 0; | 322 blacklist_info->count = 0; |
| 335 blacklist_info->reason = MIN_PROBLEM_CODE; | 323 blacklist_info->reason = SDCH_OK; |
| 336 } | 324 } |
| 337 | 325 |
| 338 int SdchManager::BlackListDomainCount(const std::string& domain) { | 326 int SdchManager::BlackListDomainCount(const std::string& domain) { |
| 339 std::string domain_lower(base::StringToLowerASCII(domain)); | 327 std::string domain_lower(base::StringToLowerASCII(domain)); |
| 340 | 328 |
| 341 if (blacklisted_domains_.end() == blacklisted_domains_.find(domain_lower)) | 329 if (blacklisted_domains_.end() == blacklisted_domains_.find(domain_lower)) |
| 342 return 0; | 330 return 0; |
| 343 return blacklisted_domains_[domain_lower].count; | 331 return blacklisted_domains_[domain_lower].count; |
| 344 } | 332 } |
| 345 | 333 |
| 346 int SdchManager::BlacklistDomainExponential(const std::string& domain) { | 334 int SdchManager::BlacklistDomainExponential(const std::string& domain) { |
| 347 std::string domain_lower(base::StringToLowerASCII(domain)); | 335 std::string domain_lower(base::StringToLowerASCII(domain)); |
| 348 | 336 |
| 349 if (blacklisted_domains_.end() == blacklisted_domains_.find(domain_lower)) | 337 if (blacklisted_domains_.end() == blacklisted_domains_.find(domain_lower)) |
| 350 return 0; | 338 return 0; |
| 351 return blacklisted_domains_[domain_lower].exponential_count; | 339 return blacklisted_domains_[domain_lower].exponential_count; |
| 352 } | 340 } |
| 353 | 341 |
| 354 bool SdchManager::IsInSupportedDomain(const GURL& url) { | 342 SdchProblemCode SdchManager::IsInSupportedDomain(const GURL& url) { |
| 355 DCHECK(CalledOnValidThread()); | 343 DCHECK(CalledOnValidThread()); |
| 356 if (!g_sdch_enabled_ ) | 344 if (!g_sdch_enabled_ ) |
| 357 return false; | 345 return SDCH_DISABLED; |
| 358 | 346 |
| 359 if (!secure_scheme_supported() && url.SchemeIsSecure()) | 347 if (!secure_scheme_supported() && url.SchemeIsSecure()) |
| 360 return false; | 348 return SDCH_SECURE_SCHEME_NOT_SUPPORTED; |
| 361 | 349 |
| 362 if (blacklisted_domains_.empty()) | 350 if (blacklisted_domains_.empty()) |
| 363 return true; | 351 return SDCH_OK; |
| 364 | 352 |
| 365 DomainBlacklistInfo::iterator it = | 353 DomainBlacklistInfo::iterator it = |
| 366 blacklisted_domains_.find(base::StringToLowerASCII(url.host())); | 354 blacklisted_domains_.find(base::StringToLowerASCII(url.host())); |
| 367 if (blacklisted_domains_.end() == it || it->second.count == 0) | 355 if (blacklisted_domains_.end() == it || it->second.count == 0) |
| 368 return true; | 356 return SDCH_OK; |
| 369 | 357 |
| 370 UMA_HISTOGRAM_ENUMERATION("Sdch3.BlacklistReason", it->second.reason, | 358 UMA_HISTOGRAM_ENUMERATION( |
| 371 MAX_PROBLEM_CODE); | 359 "Sdch3.BlacklistReason", it->second.reason, SDCH_MAX_PROBLEM_CODE); |
| 372 SdchErrorRecovery(DOMAIN_BLACKLIST_INCLUDES_TARGET); | |
| 373 | 360 |
| 374 int count = it->second.count - 1; | 361 int count = it->second.count - 1; |
| 375 if (count > 0) { | 362 if (count > 0) { |
| 376 it->second.count = count; | 363 it->second.count = count; |
| 377 } else { | 364 } else { |
| 378 it->second.count = 0; | 365 it->second.count = 0; |
| 379 it->second.reason = MIN_PROBLEM_CODE; | 366 it->second.reason = SDCH_OK; |
| 380 } | 367 } |
| 381 | 368 |
| 382 return false; | 369 return SDCH_DOMAIN_BLACKLIST_INCLUDES_TARGET; |
| 383 } | 370 } |
| 384 | 371 |
| 385 void SdchManager::FetchDictionary(const GURL& request_url, | 372 SdchProblemCode SdchManager::FetchDictionary(const GURL& request_url, |
| 386 const GURL& dictionary_url) { | 373 const GURL& dictionary_url) { |
| 387 DCHECK(CalledOnValidThread()); | 374 DCHECK(CalledOnValidThread()); |
| 388 if (CanFetchDictionary(request_url, dictionary_url) && fetcher_.get()) { | 375 SdchProblemCode rv = CanFetchDictionary(request_url, dictionary_url); |
| 376 if (rv != SDCH_OK) |
| 377 return rv; |
| 378 |
| 379 if (fetcher_.get()) { |
| 389 ++fetches_count_for_testing_; | 380 ++fetches_count_for_testing_; |
| 390 fetcher_->Schedule(dictionary_url); | 381 if (fetcher_->Schedule(dictionary_url)) |
| 382 return SDCH_DICTIONARY_PREVIOUSLY_SCHEDULED_TO_DOWNLOAD; |
| 391 } | 383 } |
| 384 |
| 385 return SDCH_OK; |
| 392 } | 386 } |
| 393 | 387 |
| 394 bool SdchManager::CanFetchDictionary(const GURL& referring_url, | 388 SdchProblemCode SdchManager::CanFetchDictionary( |
| 395 const GURL& dictionary_url) const { | 389 const GURL& referring_url, |
| 390 const GURL& dictionary_url) const { |
| 396 DCHECK(CalledOnValidThread()); | 391 DCHECK(CalledOnValidThread()); |
| 397 /* The user agent may retrieve a dictionary from the dictionary URL if all of | 392 /* The user agent may retrieve a dictionary from the dictionary URL if all of |
| 398 the following are true: | 393 the following are true: |
| 399 1 The dictionary URL host name matches the referrer URL host name and | 394 1 The dictionary URL host name matches the referrer URL host name and |
| 400 scheme. | 395 scheme. |
| 401 2 The dictionary URL host name domain matches the parent domain of the | 396 2 The dictionary URL host name domain matches the parent domain of the |
| 402 referrer URL host name | 397 referrer URL host name |
| 403 3 The parent domain of the referrer URL host name is not a top level | 398 3 The parent domain of the referrer URL host name is not a top level |
| 404 domain | 399 domain |
| 405 4 The dictionary URL is not an HTTPS URL. | 400 4 The dictionary URL is not an HTTPS URL. |
| 406 */ | 401 */ |
| 407 // Item (1) above implies item (2). Spec should be updated. | 402 // Item (1) above implies item (2). Spec should be updated. |
| 408 // I take "host name match" to be "is identical to" | 403 // I take "host name match" to be "is identical to" |
| 409 if (referring_url.host() != dictionary_url.host() || | 404 if (referring_url.host() != dictionary_url.host() || |
| 410 referring_url.scheme() != dictionary_url.scheme()) { | 405 referring_url.scheme() != dictionary_url.scheme()) |
| 411 SdchErrorRecovery(DICTIONARY_LOAD_ATTEMPT_FROM_DIFFERENT_HOST); | 406 return SDCH_DICTIONARY_LOAD_ATTEMPT_FROM_DIFFERENT_HOST; |
| 412 return false; | 407 |
| 413 } | 408 if (!secure_scheme_supported() && referring_url.SchemeIsSecure()) |
| 414 if (!secure_scheme_supported() && referring_url.SchemeIsSecure()) { | 409 return SDCH_DICTIONARY_SELECTED_FOR_SSL; |
| 415 SdchErrorRecovery(DICTIONARY_SELECTED_FOR_SSL); | |
| 416 return false; | |
| 417 } | |
| 418 | 410 |
| 419 // TODO(jar): Remove this failsafe conservative hack which is more restrictive | 411 // TODO(jar): Remove this failsafe conservative hack which is more restrictive |
| 420 // than current SDCH spec when needed, and justified by security audit. | 412 // than current SDCH spec when needed, and justified by security audit. |
| 421 if (!referring_url.SchemeIsHTTPOrHTTPS()) { | 413 if (!referring_url.SchemeIsHTTPOrHTTPS()) |
| 422 SdchErrorRecovery(DICTIONARY_SELECTED_FROM_NON_HTTP); | 414 return SDCH_DICTIONARY_SELECTED_FROM_NON_HTTP; |
| 423 return false; | |
| 424 } | |
| 425 | 415 |
| 426 return true; | 416 return SDCH_OK; |
| 427 } | 417 } |
| 428 | 418 |
| 429 void SdchManager::GetVcdiffDictionary( | 419 SdchProblemCode SdchManager::GetVcdiffDictionary( |
| 430 const std::string& server_hash, | 420 const std::string& server_hash, |
| 431 const GURL& referring_url, | 421 const GURL& referring_url, |
| 432 scoped_refptr<Dictionary>* dictionary) { | 422 scoped_refptr<Dictionary>* dictionary) { |
| 433 DCHECK(CalledOnValidThread()); | 423 DCHECK(CalledOnValidThread()); |
| 434 *dictionary = NULL; | 424 *dictionary = NULL; |
| 435 DictionaryMap::iterator it = dictionaries_.find(server_hash); | 425 DictionaryMap::iterator it = dictionaries_.find(server_hash); |
| 436 if (it == dictionaries_.end()) { | 426 if (it == dictionaries_.end()) |
| 437 return; | 427 return SDCH_DICTIONARY_HASH_NOT_FOUND; |
| 438 } | 428 |
| 439 scoped_refptr<Dictionary> matching_dictionary = it->second; | 429 scoped_refptr<Dictionary> matching_dictionary = it->second; |
| 440 if (!IsInSupportedDomain(referring_url)) | 430 |
| 441 return; | 431 SdchProblemCode rv = IsInSupportedDomain(referring_url); |
| 442 if (!matching_dictionary->CanUse(referring_url)) | 432 if (rv != SDCH_OK) |
| 443 return; | 433 return rv; |
| 444 *dictionary = matching_dictionary; | 434 |
| 435 rv = matching_dictionary->CanUse(referring_url); |
| 436 if (rv == SDCH_OK) |
| 437 *dictionary = matching_dictionary; |
| 438 return rv; |
| 445 } | 439 } |
| 446 | 440 |
| 447 // TODO(jar): If we have evictions from the dictionaries_, then we need to | 441 // TODO(jar): If we have evictions from the dictionaries_, then we need to |
| 448 // change this interface to return a list of reference counted Dictionary | 442 // change this interface to return a list of reference counted Dictionary |
| 449 // instances that can be used if/when a server specifies one. | 443 // instances that can be used if/when a server specifies one. |
| 450 void SdchManager::GetAvailDictionaryList(const GURL& target_url, | 444 void SdchManager::GetAvailDictionaryList(const GURL& target_url, |
| 451 std::string* list) { | 445 std::string* list) { |
| 452 DCHECK(CalledOnValidThread()); | 446 DCHECK(CalledOnValidThread()); |
| 453 int count = 0; | 447 int count = 0; |
| 454 for (DictionaryMap::iterator it = dictionaries_.begin(); | 448 for (DictionaryMap::iterator it = dictionaries_.begin(); |
| 455 it != dictionaries_.end(); ++it) { | 449 it != dictionaries_.end(); ++it) { |
| 456 if (!IsInSupportedDomain(target_url)) | 450 SdchProblemCode rv = IsInSupportedDomain(target_url); |
| 451 if (rv != SDCH_OK) |
| 457 continue; | 452 continue; |
| 458 if (!it->second->CanAdvertise(target_url)) | 453 |
| 454 if (it->second->CanAdvertise(target_url) != SDCH_OK) |
| 459 continue; | 455 continue; |
| 460 ++count; | 456 ++count; |
| 461 if (!list->empty()) | 457 if (!list->empty()) |
| 462 list->append(","); | 458 list->append(","); |
| 463 list->append(it->second->client_hash()); | 459 list->append(it->second->client_hash()); |
| 464 } | 460 } |
| 465 // Watch to see if we have corrupt or numerous dictionaries. | 461 // Watch to see if we have corrupt or numerous dictionaries. |
| 466 if (count > 0) | 462 if (count > 0) |
| 467 UMA_HISTOGRAM_COUNTS("Sdch3.Advertisement_Count", count); | 463 UMA_HISTOGRAM_COUNTS("Sdch3.Advertisement_Count", count); |
| 468 } | 464 } |
| (...skipping 24 matching lines...) Expand all Loading... |
| 493 | 489 |
| 494 void SdchManager::SetAllowLatencyExperiment(const GURL& url, bool enable) { | 490 void SdchManager::SetAllowLatencyExperiment(const GURL& url, bool enable) { |
| 495 DCHECK(CalledOnValidThread()); | 491 DCHECK(CalledOnValidThread()); |
| 496 if (enable) { | 492 if (enable) { |
| 497 allow_latency_experiment_.insert(url.host()); | 493 allow_latency_experiment_.insert(url.host()); |
| 498 return; | 494 return; |
| 499 } | 495 } |
| 500 ExperimentSet::iterator it = allow_latency_experiment_.find(url.host()); | 496 ExperimentSet::iterator it = allow_latency_experiment_.find(url.host()); |
| 501 if (allow_latency_experiment_.end() == it) | 497 if (allow_latency_experiment_.end() == it) |
| 502 return; // It was already erased, or never allowed. | 498 return; // It was already erased, or never allowed. |
| 503 SdchErrorRecovery(LATENCY_TEST_DISALLOWED); | 499 SdchErrorRecovery(SDCH_LATENCY_TEST_DISALLOWED); |
| 504 allow_latency_experiment_.erase(it); | 500 allow_latency_experiment_.erase(it); |
| 505 } | 501 } |
| 506 | 502 |
| 507 void SdchManager::AddSdchDictionary(const std::string& dictionary_text, | 503 SdchProblemCode SdchManager::AddSdchDictionary( |
| 504 const std::string& dictionary_text, |
| 508 const GURL& dictionary_url) { | 505 const GURL& dictionary_url) { |
| 509 DCHECK(CalledOnValidThread()); | 506 DCHECK(CalledOnValidThread()); |
| 510 std::string client_hash; | 507 std::string client_hash; |
| 511 std::string server_hash; | 508 std::string server_hash; |
| 512 GenerateHash(dictionary_text, &client_hash, &server_hash); | 509 GenerateHash(dictionary_text, &client_hash, &server_hash); |
| 513 if (dictionaries_.find(server_hash) != dictionaries_.end()) { | 510 if (dictionaries_.find(server_hash) != dictionaries_.end()) |
| 514 SdchErrorRecovery(DICTIONARY_ALREADY_LOADED); | 511 return SDCH_DICTIONARY_ALREADY_LOADED; // Already loaded. |
| 515 return; // Already loaded. | |
| 516 } | |
| 517 | 512 |
| 518 std::string domain, path; | 513 std::string domain, path; |
| 519 std::set<int> ports; | 514 std::set<int> ports; |
| 520 base::Time expiration(base::Time::Now() + base::TimeDelta::FromDays(30)); | 515 base::Time expiration(base::Time::Now() + base::TimeDelta::FromDays(30)); |
| 521 | 516 |
| 522 if (dictionary_text.empty()) { | 517 if (dictionary_text.empty()) |
| 523 SdchErrorRecovery(DICTIONARY_HAS_NO_TEXT); | 518 return SDCH_DICTIONARY_HAS_NO_TEXT; // Missing header. |
| 524 return; // Missing header. | |
| 525 } | |
| 526 | 519 |
| 527 size_t header_end = dictionary_text.find("\n\n"); | 520 size_t header_end = dictionary_text.find("\n\n"); |
| 528 if (std::string::npos == header_end) { | 521 if (std::string::npos == header_end) |
| 529 SdchErrorRecovery(DICTIONARY_HAS_NO_HEADER); | 522 return SDCH_DICTIONARY_HAS_NO_HEADER; // Missing header. |
| 530 return; // Missing header. | 523 |
| 531 } | |
| 532 size_t line_start = 0; // Start of line being parsed. | 524 size_t line_start = 0; // Start of line being parsed. |
| 533 while (1) { | 525 while (1) { |
| 534 size_t line_end = dictionary_text.find('\n', line_start); | 526 size_t line_end = dictionary_text.find('\n', line_start); |
| 535 DCHECK(std::string::npos != line_end); | 527 DCHECK(std::string::npos != line_end); |
| 536 DCHECK_LE(line_end, header_end); | 528 DCHECK_LE(line_end, header_end); |
| 537 | 529 |
| 538 size_t colon_index = dictionary_text.find(':', line_start); | 530 size_t colon_index = dictionary_text.find(':', line_start); |
| 539 if (std::string::npos == colon_index) { | 531 if (std::string::npos == colon_index) |
| 540 SdchErrorRecovery(DICTIONARY_HEADER_LINE_MISSING_COLON); | 532 return SDCH_DICTIONARY_HEADER_LINE_MISSING_COLON; // Illegal line missing |
| 541 return; // Illegal line missing a colon. | 533 // a colon. |
| 542 } | |
| 543 | 534 |
| 544 if (colon_index > line_end) | 535 if (colon_index > line_end) |
| 545 break; | 536 break; |
| 546 | 537 |
| 547 size_t value_start = dictionary_text.find_first_not_of(" \t", | 538 size_t value_start = dictionary_text.find_first_not_of(" \t", |
| 548 colon_index + 1); | 539 colon_index + 1); |
| 549 if (std::string::npos != value_start) { | 540 if (std::string::npos != value_start) { |
| 550 if (value_start >= line_end) | 541 if (value_start >= line_end) |
| 551 break; | 542 break; |
| 552 std::string name(dictionary_text, line_start, colon_index - line_start); | 543 std::string name(dictionary_text, line_start, colon_index - line_start); |
| 553 std::string value(dictionary_text, value_start, line_end - value_start); | 544 std::string value(dictionary_text, value_start, line_end - value_start); |
| 554 name = base::StringToLowerASCII(name); | 545 name = base::StringToLowerASCII(name); |
| 555 if (name == "domain") { | 546 if (name == "domain") { |
| 556 domain = value; | 547 domain = value; |
| 557 } else if (name == "path") { | 548 } else if (name == "path") { |
| 558 path = value; | 549 path = value; |
| 559 } else if (name == "format-version") { | 550 } else if (name == "format-version") { |
| 560 if (value != "1.0") | 551 if (value != "1.0") |
| 561 return; | 552 return SDCH_DICTIONARY_UNSUPPORTED_VERSION; |
| 562 } else if (name == "max-age") { | 553 } else if (name == "max-age") { |
| 563 int64 seconds; | 554 int64 seconds; |
| 564 base::StringToInt64(value, &seconds); | 555 base::StringToInt64(value, &seconds); |
| 565 expiration = base::Time::Now() + base::TimeDelta::FromSeconds(seconds); | 556 expiration = base::Time::Now() + base::TimeDelta::FromSeconds(seconds); |
| 566 } else if (name == "port") { | 557 } else if (name == "port") { |
| 567 int port; | 558 int port; |
| 568 base::StringToInt(value, &port); | 559 base::StringToInt(value, &port); |
| 569 if (port >= 0) | 560 if (port >= 0) |
| 570 ports.insert(port); | 561 ports.insert(port); |
| 571 } | 562 } |
| 572 } | 563 } |
| 573 | 564 |
| 574 if (line_end >= header_end) | 565 if (line_end >= header_end) |
| 575 break; | 566 break; |
| 576 line_start = line_end + 1; | 567 line_start = line_end + 1; |
| 577 } | 568 } |
| 578 | 569 |
| 579 // Narrow fix for http://crbug.com/389451. | 570 // Narrow fix for http://crbug.com/389451. |
| 580 GURL dictionary_url_normalized(dictionary_url); | 571 GURL dictionary_url_normalized(dictionary_url); |
| 581 StripTrailingDot(&dictionary_url_normalized); | 572 StripTrailingDot(&dictionary_url_normalized); |
| 582 | 573 |
| 583 if (!IsInSupportedDomain(dictionary_url_normalized)) | 574 SdchProblemCode rv = IsInSupportedDomain(dictionary_url_normalized); |
| 584 return; | 575 if (rv != SDCH_OK) |
| 576 return rv; |
| 585 | 577 |
| 586 if (!Dictionary::CanSet(domain, path, ports, dictionary_url_normalized)) | 578 rv = Dictionary::CanSet(domain, path, ports, dictionary_url_normalized); |
| 587 return; | 579 if (rv != SDCH_OK) |
| 580 return rv; |
| 588 | 581 |
| 589 // TODO(jar): Remove these hacks to preclude a DOS attack involving piles of | 582 // TODO(jar): Remove these hacks to preclude a DOS attack involving piles of |
| 590 // useless dictionaries. We should probably have a cache eviction plan, | 583 // useless dictionaries. We should probably have a cache eviction plan, |
| 591 // instead of just blocking additions. For now, with the spec in flux, it | 584 // instead of just blocking additions. For now, with the spec in flux, it |
| 592 // is probably not worth doing eviction handling. | 585 // is probably not worth doing eviction handling. |
| 593 if (kMaxDictionarySize < dictionary_text.size()) { | 586 if (kMaxDictionarySize < dictionary_text.size()) |
| 594 SdchErrorRecovery(DICTIONARY_IS_TOO_LARGE); | 587 return SDCH_DICTIONARY_IS_TOO_LARGE; |
| 595 return; | 588 |
| 596 } | 589 if (kMaxDictionaryCount <= dictionaries_.size()) |
| 597 if (kMaxDictionaryCount <= dictionaries_.size()) { | 590 return SDCH_DICTIONARY_COUNT_EXCEEDED; |
| 598 SdchErrorRecovery(DICTIONARY_COUNT_EXCEEDED); | |
| 599 return; | |
| 600 } | |
| 601 | 591 |
| 602 UMA_HISTOGRAM_COUNTS("Sdch3.Dictionary size loaded", dictionary_text.size()); | 592 UMA_HISTOGRAM_COUNTS("Sdch3.Dictionary size loaded", dictionary_text.size()); |
| 603 DVLOG(1) << "Loaded dictionary with client hash " << client_hash | 593 DVLOG(1) << "Loaded dictionary with client hash " << client_hash |
| 604 << " and server hash " << server_hash; | 594 << " and server hash " << server_hash; |
| 605 Dictionary* dictionary = | 595 Dictionary* dictionary = |
| 606 new Dictionary(dictionary_text, header_end + 2, client_hash, | 596 new Dictionary(dictionary_text, header_end + 2, client_hash, |
| 607 dictionary_url_normalized, domain, | 597 dictionary_url_normalized, domain, |
| 608 path, expiration, ports); | 598 path, expiration, ports); |
| 609 dictionaries_[server_hash] = dictionary; | 599 dictionaries_[server_hash] = dictionary; |
| 610 return; | 600 return SDCH_OK; |
| 611 } | 601 } |
| 612 | 602 |
| 613 // static | 603 // static |
| 614 void SdchManager::UrlSafeBase64Encode(const std::string& input, | 604 void SdchManager::UrlSafeBase64Encode(const std::string& input, |
| 615 std::string* output) { | 605 std::string* output) { |
| 616 // Since this is only done during a dictionary load, and hashes are only 8 | 606 // Since this is only done during a dictionary load, and hashes are only 8 |
| 617 // characters, we just do the simple fixup, rather than rewriting the encoder. | 607 // characters, we just do the simple fixup, rather than rewriting the encoder. |
| 618 base::Base64Encode(input, output); | 608 base::Base64Encode(input, output); |
| 619 std::replace(output->begin(), output->end(), '+', '-'); | 609 std::replace(output->begin(), output->end(), '+', '-'); |
| 620 std::replace(output->begin(), output->end(), '/', '_'); | 610 std::replace(output->begin(), output->end(), '/', '_'); |
| 621 } | 611 } |
| 622 | 612 |
| 613 base::Value* SdchManager::SdchInfoToValue() const { |
| 614 base::DictionaryValue* value = new base::DictionaryValue(); |
| 615 |
| 616 value->SetBoolean("sdch_enabled", sdch_enabled()); |
| 617 value->SetBoolean("secure_scheme_support", secure_scheme_supported()); |
| 618 |
| 619 base::ListValue* entry_list = new base::ListValue(); |
| 620 for (DictionaryMap::const_iterator it = dictionaries_.begin(); |
| 621 it != dictionaries_.end(); |
| 622 ++it) { |
| 623 base::DictionaryValue* entry_dict = new base::DictionaryValue(); |
| 624 entry_dict->SetString("url", it->second->url().spec()); |
| 625 entry_dict->SetString("client_hash", it->second->client_hash()); |
| 626 entry_dict->SetString("domain", it->second->domain()); |
| 627 entry_dict->SetString("path", it->second->path()); |
| 628 base::ListValue* port_list = new base::ListValue(); |
| 629 for (std::set<int>::const_iterator port_it = it->second->ports().begin(); |
| 630 port_it != it->second->ports().end(); |
| 631 ++port_it) { |
| 632 port_list->AppendInteger(*port_it); |
| 633 } |
| 634 entry_dict->Set("ports", port_list); |
| 635 entry_dict->SetString("server_hash", it->first); |
| 636 entry_list->Append(entry_dict); |
| 637 } |
| 638 value->Set("dictionaries", entry_list); |
| 639 |
| 640 entry_list = new base::ListValue(); |
| 641 for (DomainBlacklistInfo::const_iterator it = blacklisted_domains_.begin(); |
| 642 it != blacklisted_domains_.end(); |
| 643 ++it) { |
| 644 if (it->second.count == 0) |
| 645 continue; |
| 646 base::DictionaryValue* entry_dict = new base::DictionaryValue(); |
| 647 entry_dict->SetString("domain", it->first); |
| 648 if (it->second.count != INT_MAX) |
| 649 entry_dict->SetInteger("tries", it->second.count); |
| 650 entry_dict->SetInteger("reason", it->second.reason); |
| 651 entry_list->Append(entry_dict); |
| 652 } |
| 653 value->Set("blacklisted", entry_list); |
| 654 |
| 655 return value; |
| 656 } |
| 657 |
| 623 } // namespace net | 658 } // namespace net |
| OLD | NEW |