| 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" | 
| (...skipping 382 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 393   // TODO(jar): Remove this failsafe conservative hack which is more restrictive | 393   // TODO(jar): Remove this failsafe conservative hack which is more restrictive | 
| 394   // than current SDCH spec when needed, and justified by security audit. | 394   // than current SDCH spec when needed, and justified by security audit. | 
| 395   if (!referring_url.SchemeIsHTTPOrHTTPS()) { | 395   if (!referring_url.SchemeIsHTTPOrHTTPS()) { | 
| 396     SdchErrorRecovery(DICTIONARY_SELECTED_FROM_NON_HTTP); | 396     SdchErrorRecovery(DICTIONARY_SELECTED_FROM_NON_HTTP); | 
| 397     return false; | 397     return false; | 
| 398   } | 398   } | 
| 399 | 399 | 
| 400   return true; | 400   return true; | 
| 401 } | 401 } | 
| 402 | 402 | 
| 403 bool SdchManager::AddSdchDictionary(const std::string& dictionary_text, | 403 void SdchManager::GetVcdiffDictionary( | 
|  | 404     const std::string& server_hash, | 
|  | 405     const GURL& referring_url, | 
|  | 406     scoped_refptr<Dictionary>* dictionary) { | 
|  | 407   DCHECK(CalledOnValidThread()); | 
|  | 408   *dictionary = NULL; | 
|  | 409   DictionaryMap::iterator it = dictionaries_.find(server_hash); | 
|  | 410   if (it == dictionaries_.end()) { | 
|  | 411     return; | 
|  | 412   } | 
|  | 413   scoped_refptr<Dictionary> matching_dictionary = it->second; | 
|  | 414   if (!IsInSupportedDomain(referring_url)) | 
|  | 415     return; | 
|  | 416   if (!matching_dictionary->CanUse(referring_url)) | 
|  | 417     return; | 
|  | 418   *dictionary = matching_dictionary; | 
|  | 419 } | 
|  | 420 | 
|  | 421 // TODO(jar): If we have evictions from the dictionaries_, then we need to | 
|  | 422 // change this interface to return a list of reference counted Dictionary | 
|  | 423 // instances that can be used if/when a server specifies one. | 
|  | 424 void SdchManager::GetAvailDictionaryList(const GURL& target_url, | 
|  | 425                                          std::string* list) { | 
|  | 426   DCHECK(CalledOnValidThread()); | 
|  | 427   int count = 0; | 
|  | 428   for (DictionaryMap::iterator it = dictionaries_.begin(); | 
|  | 429        it != dictionaries_.end(); ++it) { | 
|  | 430     if (!IsInSupportedDomain(target_url)) | 
|  | 431       continue; | 
|  | 432     if (!it->second->CanAdvertise(target_url)) | 
|  | 433       continue; | 
|  | 434     ++count; | 
|  | 435     if (!list->empty()) | 
|  | 436       list->append(","); | 
|  | 437     list->append(it->second->client_hash()); | 
|  | 438   } | 
|  | 439   // Watch to see if we have corrupt or numerous dictionaries. | 
|  | 440   if (count > 0) | 
|  | 441     UMA_HISTOGRAM_COUNTS("Sdch3.Advertisement_Count", count); | 
|  | 442 } | 
|  | 443 | 
|  | 444 // static | 
|  | 445 void SdchManager::GenerateHash(const std::string& dictionary_text, | 
|  | 446     std::string* client_hash, std::string* server_hash) { | 
|  | 447   char binary_hash[32]; | 
|  | 448   crypto::SHA256HashString(dictionary_text, binary_hash, sizeof(binary_hash)); | 
|  | 449 | 
|  | 450   std::string first_48_bits(&binary_hash[0], 6); | 
|  | 451   std::string second_48_bits(&binary_hash[6], 6); | 
|  | 452   UrlSafeBase64Encode(first_48_bits, client_hash); | 
|  | 453   UrlSafeBase64Encode(second_48_bits, server_hash); | 
|  | 454 | 
|  | 455   DCHECK_EQ(server_hash->length(), 8u); | 
|  | 456   DCHECK_EQ(client_hash->length(), 8u); | 
|  | 457 } | 
|  | 458 | 
|  | 459 //------------------------------------------------------------------------------ | 
|  | 460 // Methods for supporting latency experiments. | 
|  | 461 | 
|  | 462 bool SdchManager::AllowLatencyExperiment(const GURL& url) const { | 
|  | 463   DCHECK(CalledOnValidThread()); | 
|  | 464   return allow_latency_experiment_.end() != | 
|  | 465       allow_latency_experiment_.find(url.host()); | 
|  | 466 } | 
|  | 467 | 
|  | 468 void SdchManager::SetAllowLatencyExperiment(const GURL& url, bool enable) { | 
|  | 469   DCHECK(CalledOnValidThread()); | 
|  | 470   if (enable) { | 
|  | 471     allow_latency_experiment_.insert(url.host()); | 
|  | 472     return; | 
|  | 473   } | 
|  | 474   ExperimentSet::iterator it = allow_latency_experiment_.find(url.host()); | 
|  | 475   if (allow_latency_experiment_.end() == it) | 
|  | 476     return;  // It was already erased, or never allowed. | 
|  | 477   SdchErrorRecovery(LATENCY_TEST_DISALLOWED); | 
|  | 478   allow_latency_experiment_.erase(it); | 
|  | 479 } | 
|  | 480 | 
|  | 481 void SdchManager::AddSdchDictionary(const std::string& dictionary_text, | 
| 404     const GURL& dictionary_url) { | 482     const GURL& dictionary_url) { | 
| 405   DCHECK(CalledOnValidThread()); | 483   DCHECK(CalledOnValidThread()); | 
| 406   std::string client_hash; | 484   std::string client_hash; | 
| 407   std::string server_hash; | 485   std::string server_hash; | 
| 408   GenerateHash(dictionary_text, &client_hash, &server_hash); | 486   GenerateHash(dictionary_text, &client_hash, &server_hash); | 
| 409   if (dictionaries_.find(server_hash) != dictionaries_.end()) { | 487   if (dictionaries_.find(server_hash) != dictionaries_.end()) { | 
| 410     SdchErrorRecovery(DICTIONARY_ALREADY_LOADED); | 488     SdchErrorRecovery(DICTIONARY_ALREADY_LOADED); | 
| 411     return false;  // Already loaded. | 489     return;                             // Already loaded. | 
| 412   } | 490   } | 
| 413 | 491 | 
| 414   std::string domain, path; | 492   std::string domain, path; | 
| 415   std::set<int> ports; | 493   std::set<int> ports; | 
| 416   base::Time expiration(base::Time::Now() + base::TimeDelta::FromDays(30)); | 494   base::Time expiration(base::Time::Now() + base::TimeDelta::FromDays(30)); | 
| 417 | 495 | 
| 418   if (dictionary_text.empty()) { | 496   if (dictionary_text.empty()) { | 
| 419     SdchErrorRecovery(DICTIONARY_HAS_NO_TEXT); | 497     SdchErrorRecovery(DICTIONARY_HAS_NO_TEXT); | 
| 420     return false;  // Missing header. | 498     return;                             // Missing header. | 
| 421   } | 499   } | 
| 422 | 500 | 
| 423   size_t header_end = dictionary_text.find("\n\n"); | 501   size_t header_end = dictionary_text.find("\n\n"); | 
| 424   if (std::string::npos == header_end) { | 502   if (std::string::npos == header_end) { | 
| 425     SdchErrorRecovery(DICTIONARY_HAS_NO_HEADER); | 503     SdchErrorRecovery(DICTIONARY_HAS_NO_HEADER); | 
| 426     return false;  // Missing header. | 504     return;                             // Missing header. | 
| 427   } | 505   } | 
| 428   size_t line_start = 0;  // Start of line being parsed. | 506   size_t line_start = 0;  // Start of line being parsed. | 
| 429   while (1) { | 507   while (1) { | 
| 430     size_t line_end = dictionary_text.find('\n', line_start); | 508     size_t line_end = dictionary_text.find('\n', line_start); | 
| 431     DCHECK(std::string::npos != line_end); | 509     DCHECK(std::string::npos != line_end); | 
| 432     DCHECK_LE(line_end, header_end); | 510     DCHECK_LE(line_end, header_end); | 
| 433 | 511 | 
| 434     size_t colon_index = dictionary_text.find(':', line_start); | 512     size_t colon_index = dictionary_text.find(':', line_start); | 
| 435     if (std::string::npos == colon_index) { | 513     if (std::string::npos == colon_index) { | 
| 436       SdchErrorRecovery(DICTIONARY_HEADER_LINE_MISSING_COLON); | 514       SdchErrorRecovery(DICTIONARY_HEADER_LINE_MISSING_COLON); | 
| 437       return false;  // Illegal line missing a colon. | 515       return;                         // Illegal line missing a colon. | 
| 438     } | 516     } | 
| 439 | 517 | 
| 440     if (colon_index > line_end) | 518     if (colon_index > line_end) | 
| 441       break; | 519       break; | 
| 442 | 520 | 
| 443     size_t value_start = dictionary_text.find_first_not_of(" \t", | 521     size_t value_start = dictionary_text.find_first_not_of(" \t", | 
| 444                                                            colon_index + 1); | 522                                                            colon_index + 1); | 
| 445     if (std::string::npos != value_start) { | 523     if (std::string::npos != value_start) { | 
| 446       if (value_start >= line_end) | 524       if (value_start >= line_end) | 
| 447         break; | 525         break; | 
| 448       std::string name(dictionary_text, line_start, colon_index - line_start); | 526       std::string name(dictionary_text, line_start, colon_index - line_start); | 
| 449       std::string value(dictionary_text, value_start, line_end - value_start); | 527       std::string value(dictionary_text, value_start, line_end - value_start); | 
| 450       name = base::StringToLowerASCII(name); | 528       name = base::StringToLowerASCII(name); | 
| 451       if (name == "domain") { | 529       if (name == "domain") { | 
| 452         domain = value; | 530         domain = value; | 
| 453       } else if (name == "path") { | 531       } else if (name == "path") { | 
| 454         path = value; | 532         path = value; | 
| 455       } else if (name == "format-version") { | 533       } else if (name == "format-version") { | 
| 456         if (value != "1.0") | 534         if (value != "1.0") | 
| 457           return false; | 535           return; | 
| 458       } else if (name == "max-age") { | 536       } else if (name == "max-age") { | 
| 459         int64 seconds; | 537         int64 seconds; | 
| 460         base::StringToInt64(value, &seconds); | 538         base::StringToInt64(value, &seconds); | 
| 461         expiration = base::Time::Now() + base::TimeDelta::FromSeconds(seconds); | 539         expiration = base::Time::Now() + base::TimeDelta::FromSeconds(seconds); | 
| 462       } else if (name == "port") { | 540       } else if (name == "port") { | 
| 463         int port; | 541         int port; | 
| 464         base::StringToInt(value, &port); | 542         base::StringToInt(value, &port); | 
| 465         if (port >= 0) | 543         if (port >= 0) | 
| 466           ports.insert(port); | 544           ports.insert(port); | 
| 467       } | 545       } | 
| 468     } | 546     } | 
| 469 | 547 | 
| 470     if (line_end >= header_end) | 548     if (line_end >= header_end) | 
| 471       break; | 549       break; | 
| 472     line_start = line_end + 1; | 550     line_start = line_end + 1; | 
| 473   } | 551   } | 
| 474 | 552 | 
| 475   if (!IsInSupportedDomain(dictionary_url)) | 553   if (!IsInSupportedDomain(dictionary_url)) | 
| 476     return false; | 554     return; | 
| 477 | 555 | 
| 478   if (!Dictionary::CanSet(domain, path, ports, dictionary_url)) | 556   if (!Dictionary::CanSet(domain, path, ports, dictionary_url)) | 
| 479     return false; | 557     return; | 
| 480 | 558 | 
| 481   // TODO(jar): Remove these hacks to preclude a DOS attack involving piles of | 559   // TODO(jar): Remove these hacks to preclude a DOS attack involving piles of | 
| 482   // useless dictionaries.  We should probably have a cache eviction plan, | 560   // useless dictionaries.  We should probably have a cache eviction plan, | 
| 483   // instead of just blocking additions.  For now, with the spec in flux, it | 561   // instead of just blocking additions.  For now, with the spec in flux, it | 
| 484   // is probably not worth doing eviction handling. | 562   // is probably not worth doing eviction handling. | 
| 485   if (kMaxDictionarySize < dictionary_text.size()) { | 563   if (kMaxDictionarySize < dictionary_text.size()) { | 
| 486     SdchErrorRecovery(DICTIONARY_IS_TOO_LARGE); | 564     SdchErrorRecovery(DICTIONARY_IS_TOO_LARGE); | 
| 487     return false; | 565     return; | 
| 488   } | 566   } | 
| 489   if (kMaxDictionaryCount <= dictionaries_.size()) { | 567   if (kMaxDictionaryCount <= dictionaries_.size()) { | 
| 490     SdchErrorRecovery(DICTIONARY_COUNT_EXCEEDED); | 568     SdchErrorRecovery(DICTIONARY_COUNT_EXCEEDED); | 
| 491     return false; | 569     return; | 
| 492   } | 570   } | 
| 493 | 571 | 
| 494   UMA_HISTOGRAM_COUNTS("Sdch3.Dictionary size loaded", dictionary_text.size()); | 572   UMA_HISTOGRAM_COUNTS("Sdch3.Dictionary size loaded", dictionary_text.size()); | 
| 495   DVLOG(1) << "Loaded dictionary with client hash " << client_hash | 573   DVLOG(1) << "Loaded dictionary with client hash " << client_hash | 
| 496            << " and server hash " << server_hash; | 574            << " and server hash " << server_hash; | 
| 497   Dictionary* dictionary = | 575   Dictionary* dictionary = | 
| 498       new Dictionary(dictionary_text, header_end + 2, client_hash, | 576       new Dictionary(dictionary_text, header_end + 2, client_hash, | 
| 499                      dictionary_url, domain, path, expiration, ports); | 577                      dictionary_url, domain, path, expiration, ports); | 
| 500   dictionaries_[server_hash] = dictionary; | 578   dictionaries_[server_hash] = dictionary; | 
| 501   return true; | 579   return; | 
| 502 } |  | 
| 503 |  | 
| 504 void SdchManager::GetVcdiffDictionary( |  | 
| 505     const std::string& server_hash, |  | 
| 506     const GURL& referring_url, |  | 
| 507     scoped_refptr<Dictionary>* dictionary) { |  | 
| 508   DCHECK(CalledOnValidThread()); |  | 
| 509   *dictionary = NULL; |  | 
| 510   DictionaryMap::iterator it = dictionaries_.find(server_hash); |  | 
| 511   if (it == dictionaries_.end()) { |  | 
| 512     return; |  | 
| 513   } |  | 
| 514   scoped_refptr<Dictionary> matching_dictionary = it->second; |  | 
| 515   if (!IsInSupportedDomain(referring_url)) |  | 
| 516     return; |  | 
| 517   if (!matching_dictionary->CanUse(referring_url)) |  | 
| 518     return; |  | 
| 519   *dictionary = matching_dictionary; |  | 
| 520 } |  | 
| 521 |  | 
| 522 // TODO(jar): If we have evictions from the dictionaries_, then we need to |  | 
| 523 // change this interface to return a list of reference counted Dictionary |  | 
| 524 // instances that can be used if/when a server specifies one. |  | 
| 525 void SdchManager::GetAvailDictionaryList(const GURL& target_url, |  | 
| 526                                          std::string* list) { |  | 
| 527   DCHECK(CalledOnValidThread()); |  | 
| 528   int count = 0; |  | 
| 529   for (DictionaryMap::iterator it = dictionaries_.begin(); |  | 
| 530        it != dictionaries_.end(); ++it) { |  | 
| 531     if (!IsInSupportedDomain(target_url)) |  | 
| 532       continue; |  | 
| 533     if (!it->second->CanAdvertise(target_url)) |  | 
| 534       continue; |  | 
| 535     ++count; |  | 
| 536     if (!list->empty()) |  | 
| 537       list->append(","); |  | 
| 538     list->append(it->second->client_hash()); |  | 
| 539   } |  | 
| 540   // Watch to see if we have corrupt or numerous dictionaries. |  | 
| 541   if (count > 0) |  | 
| 542     UMA_HISTOGRAM_COUNTS("Sdch3.Advertisement_Count", count); |  | 
| 543 } | 580 } | 
| 544 | 581 | 
| 545 // static | 582 // static | 
| 546 void SdchManager::GenerateHash(const std::string& dictionary_text, |  | 
| 547     std::string* client_hash, std::string* server_hash) { |  | 
| 548   char binary_hash[32]; |  | 
| 549   crypto::SHA256HashString(dictionary_text, binary_hash, sizeof(binary_hash)); |  | 
| 550 |  | 
| 551   std::string first_48_bits(&binary_hash[0], 6); |  | 
| 552   std::string second_48_bits(&binary_hash[6], 6); |  | 
| 553   UrlSafeBase64Encode(first_48_bits, client_hash); |  | 
| 554   UrlSafeBase64Encode(second_48_bits, server_hash); |  | 
| 555 |  | 
| 556   DCHECK_EQ(server_hash->length(), 8u); |  | 
| 557   DCHECK_EQ(client_hash->length(), 8u); |  | 
| 558 } |  | 
| 559 |  | 
| 560 //------------------------------------------------------------------------------ |  | 
| 561 // Methods for supporting latency experiments. |  | 
| 562 |  | 
| 563 bool SdchManager::AllowLatencyExperiment(const GURL& url) const { |  | 
| 564   DCHECK(CalledOnValidThread()); |  | 
| 565   return allow_latency_experiment_.end() != |  | 
| 566       allow_latency_experiment_.find(url.host()); |  | 
| 567 } |  | 
| 568 |  | 
| 569 void SdchManager::SetAllowLatencyExperiment(const GURL& url, bool enable) { |  | 
| 570   DCHECK(CalledOnValidThread()); |  | 
| 571   if (enable) { |  | 
| 572     allow_latency_experiment_.insert(url.host()); |  | 
| 573     return; |  | 
| 574   } |  | 
| 575   ExperimentSet::iterator it = allow_latency_experiment_.find(url.host()); |  | 
| 576   if (allow_latency_experiment_.end() == it) |  | 
| 577     return;  // It was already erased, or never allowed. |  | 
| 578   SdchErrorRecovery(LATENCY_TEST_DISALLOWED); |  | 
| 579   allow_latency_experiment_.erase(it); |  | 
| 580 } |  | 
| 581 |  | 
| 582 // static |  | 
| 583 void SdchManager::UrlSafeBase64Encode(const std::string& input, | 583 void SdchManager::UrlSafeBase64Encode(const std::string& input, | 
| 584                                       std::string* output) { | 584                                       std::string* output) { | 
| 585   // Since this is only done during a dictionary load, and hashes are only 8 | 585   // Since this is only done during a dictionary load, and hashes are only 8 | 
| 586   // characters, we just do the simple fixup, rather than rewriting the encoder. | 586   // characters, we just do the simple fixup, rather than rewriting the encoder. | 
| 587   base::Base64Encode(input, output); | 587   base::Base64Encode(input, output); | 
| 588   std::replace(output->begin(), output->end(), '+', '-'); | 588   std::replace(output->begin(), output->end(), '+', '-'); | 
| 589   std::replace(output->begin(), output->end(), '/', '_'); | 589   std::replace(output->begin(), output->end(), '/', '_'); | 
| 590 } | 590 } | 
| 591 | 591 | 
| 592 }  // namespace net | 592 }  // namespace net | 
| OLD | NEW | 
|---|