| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "net/base/sdch_manager.h" | |
| 6 | |
| 7 #include "base/base64.h" | |
| 8 #include "base/logging.h" | |
| 9 #include "base/metrics/histogram.h" | |
| 10 #include "base/strings/string_number_conversions.h" | |
| 11 #include "base/strings/string_util.h" | |
| 12 #include "base/time/default_clock.h" | |
| 13 #include "base/values.h" | |
| 14 #include "crypto/sha2.h" | |
| 15 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" | |
| 16 #include "net/base/sdch_observer.h" | |
| 17 #include "net/url_request/url_request_http_job.h" | |
| 18 | |
| 19 namespace { | |
| 20 | |
| 21 void StripTrailingDot(GURL* gurl) { | |
| 22 std::string host(gurl->host()); | |
| 23 | |
| 24 if (host.empty()) | |
| 25 return; | |
| 26 | |
| 27 if (*host.rbegin() != '.') | |
| 28 return; | |
| 29 | |
| 30 host.resize(host.size() - 1); | |
| 31 | |
| 32 GURL::Replacements replacements; | |
| 33 replacements.SetHostStr(host); | |
| 34 *gurl = gurl->ReplaceComponents(replacements); | |
| 35 return; | |
| 36 } | |
| 37 | |
| 38 } // namespace | |
| 39 | |
| 40 namespace net { | |
| 41 | |
| 42 // Workaround for http://crbug.com/437794; remove when fixed. | |
| 43 #if defined(OS_IOS) | |
| 44 // static | |
| 45 bool SdchManager::g_sdch_enabled_ = false; | |
| 46 #else | |
| 47 // static | |
| 48 bool SdchManager::g_sdch_enabled_ = true; | |
| 49 #endif | |
| 50 | |
| 51 // static | |
| 52 bool SdchManager::g_secure_scheme_supported_ = true; | |
| 53 | |
| 54 SdchManager::Dictionary::Dictionary(const std::string& dictionary_text, | |
| 55 size_t offset, | |
| 56 const std::string& client_hash, | |
| 57 const std::string& server_hash, | |
| 58 const GURL& gurl, | |
| 59 const std::string& domain, | |
| 60 const std::string& path, | |
| 61 const base::Time& expiration, | |
| 62 const std::set<int>& ports) | |
| 63 : text_(dictionary_text, offset), | |
| 64 client_hash_(client_hash), | |
| 65 server_hash_(server_hash), | |
| 66 url_(gurl), | |
| 67 domain_(domain), | |
| 68 path_(path), | |
| 69 expiration_(expiration), | |
| 70 ports_(ports), | |
| 71 clock_(new base::DefaultClock) { | |
| 72 } | |
| 73 | |
| 74 SdchManager::Dictionary::Dictionary(const SdchManager::Dictionary& rhs) | |
| 75 : text_(rhs.text_), | |
| 76 client_hash_(rhs.client_hash_), | |
| 77 server_hash_(rhs.server_hash_), | |
| 78 url_(rhs.url_), | |
| 79 domain_(rhs.domain_), | |
| 80 path_(rhs.path_), | |
| 81 expiration_(rhs.expiration_), | |
| 82 ports_(rhs.ports_), | |
| 83 clock_(new base::DefaultClock) { | |
| 84 } | |
| 85 | |
| 86 SdchManager::Dictionary::~Dictionary() {} | |
| 87 | |
| 88 // Security functions restricting loads and use of dictionaries. | |
| 89 | |
| 90 // static | |
| 91 SdchProblemCode SdchManager::Dictionary::CanSet(const std::string& domain, | |
| 92 const std::string& path, | |
| 93 const std::set<int>& ports, | |
| 94 const GURL& dictionary_url) { | |
| 95 /* | |
| 96 A dictionary is invalid and must not be stored if any of the following are | |
| 97 true: | |
| 98 1. The dictionary has no Domain attribute. | |
| 99 2. The effective host name that derives from the referer URL host name does | |
| 100 not domain-match the Domain attribute. | |
| 101 3. The Domain attribute is a top level domain. | |
| 102 4. The referer URL host is a host domain name (not IP address) and has the | |
| 103 form HD, where D is the value of the Domain attribute, and H is a string | |
| 104 that contains one or more dots. | |
| 105 5. If the dictionary has a Port attribute and the referer URL's port was not | |
| 106 in the list. | |
| 107 */ | |
| 108 | |
| 109 // TODO(jar): Redirects in dictionary fetches might plausibly be problematic, | |
| 110 // and hence the conservative approach is to not allow any redirects (if there | |
| 111 // were any... then don't allow the dictionary to be set). | |
| 112 | |
| 113 if (domain.empty()) | |
| 114 return SDCH_DICTIONARY_MISSING_DOMAIN_SPECIFIER; // Domain is required. | |
| 115 | |
| 116 if (registry_controlled_domains::GetDomainAndRegistry( | |
| 117 domain, registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES) | |
| 118 .empty()) { | |
| 119 return SDCH_DICTIONARY_SPECIFIES_TOP_LEVEL_DOMAIN; // domain was a TLD. | |
| 120 } | |
| 121 | |
| 122 if (!Dictionary::DomainMatch(dictionary_url, domain)) | |
| 123 return SDCH_DICTIONARY_DOMAIN_NOT_MATCHING_SOURCE_URL; | |
| 124 | |
| 125 std::string referrer_url_host = dictionary_url.host(); | |
| 126 size_t postfix_domain_index = referrer_url_host.rfind(domain); | |
| 127 // See if it is indeed a postfix, or just an internal string. | |
| 128 if (referrer_url_host.size() == postfix_domain_index + domain.size()) { | |
| 129 // It is a postfix... so check to see if there's a dot in the prefix. | |
| 130 size_t end_of_host_index = referrer_url_host.find_first_of('.'); | |
| 131 if (referrer_url_host.npos != end_of_host_index && | |
| 132 end_of_host_index < postfix_domain_index) { | |
| 133 return SDCH_DICTIONARY_REFERER_URL_HAS_DOT_IN_PREFIX; | |
| 134 } | |
| 135 } | |
| 136 | |
| 137 if (!ports.empty() && 0 == ports.count(dictionary_url.EffectiveIntPort())) | |
| 138 return SDCH_DICTIONARY_PORT_NOT_MATCHING_SOURCE_URL; | |
| 139 | |
| 140 return SDCH_OK; | |
| 141 } | |
| 142 | |
| 143 SdchProblemCode SdchManager::Dictionary::CanUse( | |
| 144 const GURL& target_url) const { | |
| 145 /* | |
| 146 1. The request URL's host name domain-matches the Domain attribute of the | |
| 147 dictionary. | |
| 148 2. If the dictionary has a Port attribute, the request port is one of the | |
| 149 ports listed in the Port attribute. | |
| 150 3. The request URL path-matches the path attribute of the dictionary. | |
| 151 4. The request is not an HTTPS request. | |
| 152 We can override (ignore) item (4) only when we have explicitly enabled | |
| 153 HTTPS support AND the dictionary acquisition scheme matches the target | |
| 154 url scheme. | |
| 155 */ | |
| 156 if (!DomainMatch(target_url, domain_)) | |
| 157 return SDCH_DICTIONARY_FOUND_HAS_WRONG_DOMAIN; | |
| 158 | |
| 159 if (!ports_.empty() && 0 == ports_.count(target_url.EffectiveIntPort())) | |
| 160 return SDCH_DICTIONARY_FOUND_HAS_WRONG_PORT_LIST; | |
| 161 | |
| 162 if (path_.size() && !PathMatch(target_url.path(), path_)) | |
| 163 return SDCH_DICTIONARY_FOUND_HAS_WRONG_PATH; | |
| 164 | |
| 165 if (!SdchManager::secure_scheme_supported() && target_url.SchemeIsSecure()) | |
| 166 return SDCH_DICTIONARY_FOUND_HAS_WRONG_SCHEME; | |
| 167 | |
| 168 if (target_url.SchemeIsSecure() != url_.SchemeIsSecure()) | |
| 169 return SDCH_DICTIONARY_FOUND_HAS_WRONG_SCHEME; | |
| 170 | |
| 171 // TODO(jar): Remove overly restrictive failsafe test (added per security | |
| 172 // review) when we have a need to be more general. | |
| 173 if (!target_url.SchemeIsHTTPOrHTTPS()) | |
| 174 return SDCH_ATTEMPT_TO_DECODE_NON_HTTP_DATA; | |
| 175 | |
| 176 return SDCH_OK; | |
| 177 } | |
| 178 | |
| 179 // static | |
| 180 bool SdchManager::Dictionary::PathMatch(const std::string& path, | |
| 181 const std::string& restriction) { | |
| 182 /* Must be either: | |
| 183 1. P2 is equal to P1 | |
| 184 2. P2 is a prefix of P1 and either the final character in P2 is "/" or the | |
| 185 character following P2 in P1 is "/". | |
| 186 */ | |
| 187 if (path == restriction) | |
| 188 return true; | |
| 189 size_t prefix_length = restriction.size(); | |
| 190 if (prefix_length > path.size()) | |
| 191 return false; // Can't be a prefix. | |
| 192 if (0 != path.compare(0, prefix_length, restriction)) | |
| 193 return false; | |
| 194 return restriction[prefix_length - 1] == '/' || path[prefix_length] == '/'; | |
| 195 } | |
| 196 | |
| 197 // static | |
| 198 bool SdchManager::Dictionary::DomainMatch(const GURL& gurl, | |
| 199 const std::string& restriction) { | |
| 200 // TODO(jar): This is not precisely a domain match definition. | |
| 201 return gurl.DomainIs(restriction.data(), restriction.size()); | |
| 202 } | |
| 203 | |
| 204 bool SdchManager::Dictionary::Expired() const { | |
| 205 return clock_->Now() > expiration_; | |
| 206 } | |
| 207 | |
| 208 void SdchManager::Dictionary::SetClockForTesting( | |
| 209 scoped_ptr<base::Clock> clock) { | |
| 210 clock_ = clock.Pass(); | |
| 211 } | |
| 212 | |
| 213 SdchManager::DictionarySet::DictionarySet() {} | |
| 214 | |
| 215 SdchManager::DictionarySet::~DictionarySet() {} | |
| 216 | |
| 217 std::string SdchManager::DictionarySet::GetDictionaryClientHashList() const { | |
| 218 std::string result; | |
| 219 bool first = true; | |
| 220 for (const auto& entry: dictionaries_) { | |
| 221 if (!first) | |
| 222 result.append(","); | |
| 223 | |
| 224 result.append(entry.second->data.client_hash()); | |
| 225 first = false; | |
| 226 } | |
| 227 return result; | |
| 228 } | |
| 229 | |
| 230 const SdchManager::Dictionary* SdchManager::DictionarySet::GetDictionary( | |
| 231 const std::string& hash) const { | |
| 232 auto it = dictionaries_.find(hash); | |
| 233 if (it == dictionaries_.end()) | |
| 234 return NULL; | |
| 235 | |
| 236 return &it->second->data; | |
| 237 } | |
| 238 | |
| 239 bool SdchManager::DictionarySet::Empty() const { | |
| 240 return dictionaries_.empty(); | |
| 241 } | |
| 242 | |
| 243 void SdchManager::DictionarySet::AddDictionary( | |
| 244 const std::string& server_hash, | |
| 245 const scoped_refptr<base::RefCountedData<SdchManager::Dictionary>>& | |
| 246 dictionary) { | |
| 247 DCHECK(dictionaries_.end() == dictionaries_.find(server_hash)); | |
| 248 | |
| 249 dictionaries_[server_hash] = dictionary; | |
| 250 } | |
| 251 | |
| 252 SdchManager::SdchManager() { | |
| 253 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 254 } | |
| 255 | |
| 256 SdchManager::~SdchManager() { | |
| 257 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 258 while (!dictionaries_.empty()) { | |
| 259 auto it = dictionaries_.begin(); | |
| 260 dictionaries_.erase(it->first); | |
| 261 } | |
| 262 } | |
| 263 | |
| 264 void SdchManager::ClearData() { | |
| 265 blacklisted_domains_.clear(); | |
| 266 allow_latency_experiment_.clear(); | |
| 267 dictionaries_.clear(); | |
| 268 FOR_EACH_OBSERVER(SdchObserver, observers_, OnClearDictionaries(this)); | |
| 269 } | |
| 270 | |
| 271 // static | |
| 272 void SdchManager::SdchErrorRecovery(SdchProblemCode problem) { | |
| 273 UMA_HISTOGRAM_ENUMERATION("Sdch3.ProblemCodes_5", problem, | |
| 274 SDCH_MAX_PROBLEM_CODE); | |
| 275 } | |
| 276 | |
| 277 // static | |
| 278 void SdchManager::EnableSdchSupport(bool enabled) { | |
| 279 g_sdch_enabled_ = enabled; | |
| 280 } | |
| 281 | |
| 282 // static | |
| 283 void SdchManager::EnableSecureSchemeSupport(bool enabled) { | |
| 284 g_secure_scheme_supported_ = enabled; | |
| 285 } | |
| 286 | |
| 287 void SdchManager::BlacklistDomain(const GURL& url, | |
| 288 SdchProblemCode blacklist_reason) { | |
| 289 SetAllowLatencyExperiment(url, false); | |
| 290 | |
| 291 BlacklistInfo* blacklist_info = | |
| 292 &blacklisted_domains_[base::StringToLowerASCII(url.host())]; | |
| 293 | |
| 294 if (blacklist_info->count > 0) | |
| 295 return; // Domain is already blacklisted. | |
| 296 | |
| 297 if (blacklist_info->exponential_count > (INT_MAX - 1) / 2) { | |
| 298 blacklist_info->exponential_count = INT_MAX; | |
| 299 } else { | |
| 300 blacklist_info->exponential_count = | |
| 301 blacklist_info->exponential_count * 2 + 1; | |
| 302 } | |
| 303 | |
| 304 blacklist_info->count = blacklist_info->exponential_count; | |
| 305 blacklist_info->reason = blacklist_reason; | |
| 306 } | |
| 307 | |
| 308 void SdchManager::BlacklistDomainForever(const GURL& url, | |
| 309 SdchProblemCode blacklist_reason) { | |
| 310 SetAllowLatencyExperiment(url, false); | |
| 311 | |
| 312 BlacklistInfo* blacklist_info = | |
| 313 &blacklisted_domains_[base::StringToLowerASCII(url.host())]; | |
| 314 blacklist_info->count = INT_MAX; | |
| 315 blacklist_info->exponential_count = INT_MAX; | |
| 316 blacklist_info->reason = blacklist_reason; | |
| 317 } | |
| 318 | |
| 319 void SdchManager::ClearBlacklistings() { | |
| 320 blacklisted_domains_.clear(); | |
| 321 } | |
| 322 | |
| 323 void SdchManager::ClearDomainBlacklisting(const std::string& domain) { | |
| 324 BlacklistInfo* blacklist_info = &blacklisted_domains_[ | |
| 325 base::StringToLowerASCII(domain)]; | |
| 326 blacklist_info->count = 0; | |
| 327 blacklist_info->reason = SDCH_OK; | |
| 328 } | |
| 329 | |
| 330 int SdchManager::BlackListDomainCount(const std::string& domain) { | |
| 331 std::string domain_lower(base::StringToLowerASCII(domain)); | |
| 332 | |
| 333 if (blacklisted_domains_.end() == blacklisted_domains_.find(domain_lower)) | |
| 334 return 0; | |
| 335 return blacklisted_domains_[domain_lower].count; | |
| 336 } | |
| 337 | |
| 338 int SdchManager::BlacklistDomainExponential(const std::string& domain) { | |
| 339 std::string domain_lower(base::StringToLowerASCII(domain)); | |
| 340 | |
| 341 if (blacklisted_domains_.end() == blacklisted_domains_.find(domain_lower)) | |
| 342 return 0; | |
| 343 return blacklisted_domains_[domain_lower].exponential_count; | |
| 344 } | |
| 345 | |
| 346 SdchProblemCode SdchManager::IsInSupportedDomain(const GURL& url) { | |
| 347 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 348 if (!g_sdch_enabled_ ) | |
| 349 return SDCH_DISABLED; | |
| 350 | |
| 351 if (!secure_scheme_supported() && url.SchemeIsSecure()) | |
| 352 return SDCH_SECURE_SCHEME_NOT_SUPPORTED; | |
| 353 | |
| 354 if (blacklisted_domains_.empty()) | |
| 355 return SDCH_OK; | |
| 356 | |
| 357 DomainBlacklistInfo::iterator it = | |
| 358 blacklisted_domains_.find(base::StringToLowerASCII(url.host())); | |
| 359 if (blacklisted_domains_.end() == it || it->second.count == 0) | |
| 360 return SDCH_OK; | |
| 361 | |
| 362 UMA_HISTOGRAM_ENUMERATION("Sdch3.BlacklistReason", it->second.reason, | |
| 363 SDCH_MAX_PROBLEM_CODE); | |
| 364 | |
| 365 int count = it->second.count - 1; | |
| 366 if (count > 0) { | |
| 367 it->second.count = count; | |
| 368 } else { | |
| 369 it->second.count = 0; | |
| 370 it->second.reason = SDCH_OK; | |
| 371 } | |
| 372 | |
| 373 return SDCH_DOMAIN_BLACKLIST_INCLUDES_TARGET; | |
| 374 } | |
| 375 | |
| 376 SdchProblemCode SdchManager::OnGetDictionary(const GURL& request_url, | |
| 377 const GURL& dictionary_url) { | |
| 378 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 379 SdchProblemCode rv = CanFetchDictionary(request_url, dictionary_url); | |
| 380 if (rv != SDCH_OK) | |
| 381 return rv; | |
| 382 | |
| 383 FOR_EACH_OBSERVER(SdchObserver, | |
| 384 observers_, | |
| 385 OnGetDictionary(this, request_url, dictionary_url)); | |
| 386 | |
| 387 return SDCH_OK; | |
| 388 } | |
| 389 | |
| 390 void SdchManager::OnDictionaryUsed(const std::string& server_hash) { | |
| 391 FOR_EACH_OBSERVER(SdchObserver, observers_, | |
| 392 OnDictionaryUsed(this, server_hash)); | |
| 393 } | |
| 394 | |
| 395 SdchProblemCode SdchManager::CanFetchDictionary( | |
| 396 const GURL& referring_url, | |
| 397 const GURL& dictionary_url) const { | |
| 398 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 399 /* The user agent may retrieve a dictionary from the dictionary URL if all of | |
| 400 the following are true: | |
| 401 1 The dictionary URL host name matches the referrer URL host name and | |
| 402 scheme. | |
| 403 2 The dictionary URL host name domain matches the parent domain of the | |
| 404 referrer URL host name | |
| 405 3 The parent domain of the referrer URL host name is not a top level | |
| 406 domain | |
| 407 */ | |
| 408 // Item (1) above implies item (2). Spec should be updated. | |
| 409 // I take "host name match" to be "is identical to" | |
| 410 if (referring_url.host() != dictionary_url.host() || | |
| 411 referring_url.scheme() != dictionary_url.scheme()) | |
| 412 return SDCH_DICTIONARY_LOAD_ATTEMPT_FROM_DIFFERENT_HOST; | |
| 413 | |
| 414 if (!secure_scheme_supported() && referring_url.SchemeIsSecure()) | |
| 415 return SDCH_DICTIONARY_SELECTED_FOR_SSL; | |
| 416 | |
| 417 // TODO(jar): Remove this failsafe conservative hack which is more restrictive | |
| 418 // than current SDCH spec when needed, and justified by security audit. | |
| 419 if (!referring_url.SchemeIsHTTPOrHTTPS()) | |
| 420 return SDCH_DICTIONARY_SELECTED_FROM_NON_HTTP; | |
| 421 | |
| 422 return SDCH_OK; | |
| 423 } | |
| 424 | |
| 425 scoped_ptr<SdchManager::DictionarySet> | |
| 426 SdchManager::GetDictionarySet(const GURL& target_url) { | |
| 427 if (IsInSupportedDomain(target_url) != SDCH_OK) | |
| 428 return NULL; | |
| 429 | |
| 430 int count = 0; | |
| 431 scoped_ptr<SdchManager::DictionarySet> result(new DictionarySet); | |
| 432 for (const auto& entry: dictionaries_) { | |
| 433 if (entry.second->data.CanUse(target_url) != SDCH_OK) | |
| 434 continue; | |
| 435 if (entry.second->data.Expired()) | |
| 436 continue; | |
| 437 ++count; | |
| 438 result->AddDictionary(entry.first, entry.second); | |
| 439 } | |
| 440 | |
| 441 if (count == 0) | |
| 442 return NULL; | |
| 443 | |
| 444 UMA_HISTOGRAM_COUNTS("Sdch3.Advertisement_Count", count); | |
| 445 | |
| 446 return result.Pass(); | |
| 447 } | |
| 448 | |
| 449 scoped_ptr<SdchManager::DictionarySet> | |
| 450 SdchManager::GetDictionarySetByHash( | |
| 451 const GURL& target_url, | |
| 452 const std::string& server_hash, | |
| 453 SdchProblemCode* problem_code) { | |
| 454 scoped_ptr<SdchManager::DictionarySet> result; | |
| 455 | |
| 456 *problem_code = SDCH_DICTIONARY_HASH_NOT_FOUND; | |
| 457 const auto& it = dictionaries_.find(server_hash); | |
| 458 if (it == dictionaries_.end()) | |
| 459 return result.Pass(); | |
| 460 | |
| 461 *problem_code = it->second->data.CanUse(target_url); | |
| 462 if (*problem_code != SDCH_OK) | |
| 463 return result.Pass(); | |
| 464 | |
| 465 result.reset(new DictionarySet); | |
| 466 result->AddDictionary(it->first, it->second); | |
| 467 return result.Pass(); | |
| 468 } | |
| 469 | |
| 470 // static | |
| 471 void SdchManager::GenerateHash(const std::string& dictionary_text, | |
| 472 std::string* client_hash, std::string* server_hash) { | |
| 473 char binary_hash[32]; | |
| 474 crypto::SHA256HashString(dictionary_text, binary_hash, sizeof(binary_hash)); | |
| 475 | |
| 476 std::string first_48_bits(&binary_hash[0], 6); | |
| 477 std::string second_48_bits(&binary_hash[6], 6); | |
| 478 UrlSafeBase64Encode(first_48_bits, client_hash); | |
| 479 UrlSafeBase64Encode(second_48_bits, server_hash); | |
| 480 | |
| 481 DCHECK_EQ(server_hash->length(), 8u); | |
| 482 DCHECK_EQ(client_hash->length(), 8u); | |
| 483 } | |
| 484 | |
| 485 // Methods for supporting latency experiments. | |
| 486 | |
| 487 bool SdchManager::AllowLatencyExperiment(const GURL& url) const { | |
| 488 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 489 return allow_latency_experiment_.end() != | |
| 490 allow_latency_experiment_.find(url.host()); | |
| 491 } | |
| 492 | |
| 493 void SdchManager::SetAllowLatencyExperiment(const GURL& url, bool enable) { | |
| 494 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 495 if (enable) { | |
| 496 allow_latency_experiment_.insert(url.host()); | |
| 497 return; | |
| 498 } | |
| 499 ExperimentSet::iterator it = allow_latency_experiment_.find(url.host()); | |
| 500 if (allow_latency_experiment_.end() == it) | |
| 501 return; // It was already erased, or never allowed. | |
| 502 SdchErrorRecovery(SDCH_LATENCY_TEST_DISALLOWED); | |
| 503 allow_latency_experiment_.erase(it); | |
| 504 } | |
| 505 | |
| 506 void SdchManager::AddObserver(SdchObserver* observer) { | |
| 507 observers_.AddObserver(observer); | |
| 508 } | |
| 509 | |
| 510 void SdchManager::RemoveObserver(SdchObserver* observer) { | |
| 511 observers_.RemoveObserver(observer); | |
| 512 } | |
| 513 | |
| 514 SdchProblemCode SdchManager::AddSdchDictionary( | |
| 515 const std::string& dictionary_text, | |
| 516 const GURL& dictionary_url, | |
| 517 std::string* server_hash_p) { | |
| 518 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 519 std::string client_hash; | |
| 520 std::string server_hash; | |
| 521 GenerateHash(dictionary_text, &client_hash, &server_hash); | |
| 522 if (dictionaries_.find(server_hash) != dictionaries_.end()) | |
| 523 return SDCH_DICTIONARY_ALREADY_LOADED; // Already loaded. | |
| 524 | |
| 525 std::string domain, path; | |
| 526 std::set<int> ports; | |
| 527 base::Time expiration(base::Time::Now() + base::TimeDelta::FromDays(30)); | |
| 528 | |
| 529 if (dictionary_text.empty()) | |
| 530 return SDCH_DICTIONARY_HAS_NO_TEXT; // Missing header. | |
| 531 | |
| 532 size_t header_end = dictionary_text.find("\n\n"); | |
| 533 if (std::string::npos == header_end) | |
| 534 return SDCH_DICTIONARY_HAS_NO_HEADER; // Missing header. | |
| 535 | |
| 536 size_t line_start = 0; // Start of line being parsed. | |
| 537 while (1) { | |
| 538 size_t line_end = dictionary_text.find('\n', line_start); | |
| 539 DCHECK(std::string::npos != line_end); | |
| 540 DCHECK_LE(line_end, header_end); | |
| 541 | |
| 542 size_t colon_index = dictionary_text.find(':', line_start); | |
| 543 if (std::string::npos == colon_index) | |
| 544 return SDCH_DICTIONARY_HEADER_LINE_MISSING_COLON; // Illegal line missing | |
| 545 // a colon. | |
| 546 | |
| 547 if (colon_index > line_end) | |
| 548 break; | |
| 549 | |
| 550 size_t value_start = dictionary_text.find_first_not_of(" \t", | |
| 551 colon_index + 1); | |
| 552 if (std::string::npos != value_start) { | |
| 553 if (value_start >= line_end) | |
| 554 break; | |
| 555 std::string name(dictionary_text, line_start, colon_index - line_start); | |
| 556 std::string value(dictionary_text, value_start, line_end - value_start); | |
| 557 name = base::StringToLowerASCII(name); | |
| 558 if (name == "domain") { | |
| 559 domain = value; | |
| 560 } else if (name == "path") { | |
| 561 path = value; | |
| 562 } else if (name == "format-version") { | |
| 563 if (value != "1.0") | |
| 564 return SDCH_DICTIONARY_UNSUPPORTED_VERSION; | |
| 565 } else if (name == "max-age") { | |
| 566 int64 seconds; | |
| 567 base::StringToInt64(value, &seconds); | |
| 568 expiration = base::Time::Now() + base::TimeDelta::FromSeconds(seconds); | |
| 569 } else if (name == "port") { | |
| 570 int port; | |
| 571 base::StringToInt(value, &port); | |
| 572 if (port >= 0) | |
| 573 ports.insert(port); | |
| 574 } | |
| 575 } | |
| 576 | |
| 577 if (line_end >= header_end) | |
| 578 break; | |
| 579 line_start = line_end + 1; | |
| 580 } | |
| 581 | |
| 582 // Narrow fix for http://crbug.com/389451. | |
| 583 GURL dictionary_url_normalized(dictionary_url); | |
| 584 StripTrailingDot(&dictionary_url_normalized); | |
| 585 | |
| 586 SdchProblemCode rv = IsInSupportedDomain(dictionary_url_normalized); | |
| 587 if (rv != SDCH_OK) | |
| 588 return rv; | |
| 589 | |
| 590 rv = Dictionary::CanSet(domain, path, ports, dictionary_url_normalized); | |
| 591 if (rv != SDCH_OK) | |
| 592 return rv; | |
| 593 | |
| 594 UMA_HISTOGRAM_COUNTS("Sdch3.Dictionary size loaded", dictionary_text.size()); | |
| 595 DVLOG(1) << "Loaded dictionary with client hash " << client_hash | |
| 596 << " and server hash " << server_hash; | |
| 597 Dictionary dictionary(dictionary_text, header_end + 2, client_hash, | |
| 598 server_hash, dictionary_url_normalized, domain, path, | |
| 599 expiration, ports); | |
| 600 dictionaries_[server_hash] = | |
| 601 new base::RefCountedData<Dictionary>(dictionary); | |
| 602 if (server_hash_p) | |
| 603 *server_hash_p = server_hash; | |
| 604 | |
| 605 return SDCH_OK; | |
| 606 } | |
| 607 | |
| 608 SdchProblemCode SdchManager::RemoveSdchDictionary( | |
| 609 const std::string& server_hash) { | |
| 610 if (dictionaries_.find(server_hash) == dictionaries_.end()) | |
| 611 return SDCH_DICTIONARY_HASH_NOT_FOUND; | |
| 612 | |
| 613 dictionaries_.erase(server_hash); | |
| 614 return SDCH_OK; | |
| 615 } | |
| 616 | |
| 617 // static | |
| 618 scoped_ptr<SdchManager::DictionarySet> | |
| 619 SdchManager::CreateEmptyDictionarySetForTesting() { | |
| 620 return scoped_ptr<DictionarySet>(new DictionarySet).Pass(); | |
| 621 } | |
| 622 | |
| 623 // static | |
| 624 void SdchManager::UrlSafeBase64Encode(const std::string& input, | |
| 625 std::string* output) { | |
| 626 // Since this is only done during a dictionary load, and hashes are only 8 | |
| 627 // characters, we just do the simple fixup, rather than rewriting the encoder. | |
| 628 base::Base64Encode(input, output); | |
| 629 std::replace(output->begin(), output->end(), '+', '-'); | |
| 630 std::replace(output->begin(), output->end(), '/', '_'); | |
| 631 } | |
| 632 | |
| 633 base::Value* SdchManager::SdchInfoToValue() const { | |
| 634 base::DictionaryValue* value = new base::DictionaryValue(); | |
| 635 | |
| 636 value->SetBoolean("sdch_enabled", sdch_enabled()); | |
| 637 value->SetBoolean("secure_scheme_support", secure_scheme_supported()); | |
| 638 | |
| 639 base::ListValue* entry_list = new base::ListValue(); | |
| 640 for (const auto& entry: dictionaries_) { | |
| 641 base::DictionaryValue* entry_dict = new base::DictionaryValue(); | |
| 642 entry_dict->SetString("url", entry.second->data.url().spec()); | |
| 643 entry_dict->SetString("client_hash", entry.second->data.client_hash()); | |
| 644 entry_dict->SetString("domain", entry.second->data.domain()); | |
| 645 entry_dict->SetString("path", entry.second->data.path()); | |
| 646 base::ListValue* port_list = new base::ListValue(); | |
| 647 for (std::set<int>::const_iterator port_it = | |
| 648 entry.second->data.ports().begin(); | |
| 649 port_it != entry.second->data.ports().end(); ++port_it) { | |
| 650 port_list->AppendInteger(*port_it); | |
| 651 } | |
| 652 entry_dict->Set("ports", port_list); | |
| 653 entry_dict->SetString("server_hash", entry.first); | |
| 654 entry_list->Append(entry_dict); | |
| 655 } | |
| 656 value->Set("dictionaries", entry_list); | |
| 657 | |
| 658 entry_list = new base::ListValue(); | |
| 659 for (DomainBlacklistInfo::const_iterator it = blacklisted_domains_.begin(); | |
| 660 it != blacklisted_domains_.end(); ++it) { | |
| 661 if (it->second.count == 0) | |
| 662 continue; | |
| 663 base::DictionaryValue* entry_dict = new base::DictionaryValue(); | |
| 664 entry_dict->SetString("domain", it->first); | |
| 665 if (it->second.count != INT_MAX) | |
| 666 entry_dict->SetInteger("tries", it->second.count); | |
| 667 entry_dict->SetInteger("reason", it->second.reason); | |
| 668 entry_list->Append(entry_dict); | |
| 669 } | |
| 670 value->Set("blacklisted", entry_list); | |
| 671 | |
| 672 return value; | |
| 673 } | |
| 674 | |
| 675 } // namespace net | |
| OLD | NEW |