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 |