| 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 |