Chromium Code Reviews| Index: third_party/libaddressinput/chromium/cpp/src/retriever.cc | 
| diff --git a/third_party/libaddressinput/chromium/cpp/src/retriever.cc b/third_party/libaddressinput/chromium/cpp/src/retriever.cc | 
| index eebba3eb2d78b9b5d590f6aa8a4ba2819d39d7c1..371e0f0d975393f5c6e82b7a795acb7410e998ba 100644 | 
| --- a/third_party/libaddressinput/chromium/cpp/src/retriever.cc | 
| +++ b/third_party/libaddressinput/chromium/cpp/src/retriever.cc | 
| @@ -22,22 +22,144 @@ | 
| #include <cassert> | 
| #include <cstddef> | 
| +#include <cstdlib> | 
| +#include <ctime> | 
| #include <map> | 
| #include <string> | 
| -#include <utility> | 
| #include "fallback_data_store.h" | 
| +#include "time_to_string.h" | 
| +#include "util/md5.h" | 
| #include "util/stl_util.h" | 
| namespace i18n { | 
| namespace addressinput { | 
| +namespace { | 
| + | 
| +// The number of seconds after which data is considered stale. The staleness | 
| +// threshold is 30 days: | 
| +// 30 days * | 
| +// 24 hours per day * | 
| +// 60 minutes per hour * | 
| +// 60 seconds per minute. | 
| +static const double kStaleDataAgeInSeconds = 30.0 * 24.0 * 60.0 * 60.0; | 
| + | 
| +// The prefix for the timestamp line in the header. | 
| +const char kTimestampPrefix[] = "timestamp="; | 
| +const size_t kTimestampPrefixLength = sizeof kTimestampPrefix - 1; | 
| + | 
| +// The prefix for the checksum line in the header. | 
| +const char kChecksumPrefix[] = "checksum="; | 
| +const size_t kChecksumPrefixLength = sizeof kChecksumPrefix - 1; | 
| + | 
| +// The separator between lines of header and data. | 
| +const char kSeparator = '\n'; | 
| + | 
| +// Returns |data| with attached checksum and current timestamp. Format: | 
| +// | 
| +// timestamp=<timestamp> | 
| +// checksum=<checksum> | 
| +// <data> | 
| +// | 
| +// The timestamp is the time_t that was returned from time(NULL) function. The | 
| +// timestamp does not need to be portable because it is written and read only by | 
| +// Retriever. The value is somewhat human-readable: it is the number of seconds | 
| +// since the epoch. | 
| +// | 
| +// The checksum is the 32-character hexadecimal MD5 checksum of <data>. It is | 
| +// meant to protect from random file changes on disk. | 
| +std::string PrependTimestamp(const std::string& data) { | 
| + std::string wrapped; | 
| + wrapped.append(kTimestampPrefix, kTimestampPrefixLength); | 
| + wrapped.append(TimeToString(time(NULL))); | 
| + wrapped.push_back(kSeparator); | 
| + | 
| + wrapped.append(kChecksumPrefix, kChecksumPrefixLength); | 
| + wrapped.append(MD5String(data)); | 
| + wrapped.push_back(kSeparator); | 
| + wrapped.append(data); | 
| + | 
| + return wrapped; | 
| +} | 
| + | 
| +// Places the header value into |header_value| parameter and the rest of the | 
| +// data into |data| parameter. Returns |true| if the header format is valid. | 
| +bool ExtractHeader(const std::string& header_and_data, | 
| + const char* header_prefix, | 
| + size_t header_prefix_length, | 
| + std::string* header_value, | 
| + std::string* data) { | 
| + assert(header_prefix != NULL); | 
| + assert(header_value != NULL); | 
| + assert(data != NULL); | 
| + | 
| + if (header_and_data.compare( | 
| + 0, header_prefix_length, header_prefix, header_prefix_length) != 0) { | 
| + return false; | 
| + } | 
| + | 
| + std::string::size_type separator_position = | 
| + header_and_data.find(kSeparator, header_prefix_length); | 
| + if (separator_position == std::string::npos) { | 
| + return false; | 
| + } | 
| + | 
| + data->assign(header_and_data, separator_position + 1, std::string::npos); | 
| + header_value->assign(header_and_data, header_prefix_length, | 
| + separator_position - header_prefix_length); | 
| + return true; | 
| +} | 
| + | 
| +// Strips out the timestamp and checksum from |header_and_data|. Validates the | 
| +// checksum. Saves the header-less data into |data|. Compares the parsed | 
| +// timestamp with current time and saves the difference into |age_in_seconds|. | 
| +// | 
| +// The parameters should not be NULL. Does not take ownership of its parameters. | 
| +// | 
| +// Returns |true| if |header_and_data| is correctly formatted and has the | 
| +// correct checksum. | 
| +bool VerifyAndExtractTimestamp(const std::string& header_and_data, | 
| + std::string* data, | 
| + double* age_in_seconds) { | 
| + assert(data != NULL); | 
| + assert(age_in_seconds != NULL); | 
| + | 
| + std::string timestamp_string; | 
| + std::string checksum_and_data; | 
| + if (!ExtractHeader(header_and_data, kTimestampPrefix, kTimestampPrefixLength, | 
| + ×tamp_string, &checksum_and_data)) { | 
| + return false; | 
| + } | 
| + | 
| + time_t timestamp = atol(timestamp_string.c_str()); | 
| + if (timestamp < 0) { | 
| + return false; | 
| + } | 
| + | 
| + *age_in_seconds = difftime(time(NULL), timestamp); | 
| + if (*age_in_seconds < 0.0) { | 
| + return false; | 
| + } | 
| + | 
| + std::string checksum; | 
| + if (!ExtractHeader(checksum_and_data, kChecksumPrefix, kChecksumPrefixLength, | 
| + &checksum, data)) { | 
| + return false; | 
| + } | 
| + | 
| + return checksum == MD5String(*data); | 
| +} | 
| + | 
| +} // namespace | 
| + | 
| Retriever::Retriever(const std::string& validation_data_url, | 
| scoped_ptr<Downloader> downloader, | 
| scoped_ptr<Storage> storage) | 
| : validation_data_url_(validation_data_url), | 
| downloader_(downloader.Pass()), | 
| - storage_(storage.Pass()) { | 
| + storage_(storage.Pass()), | 
| + stale_data_() { | 
| assert(validation_data_url_.length() > 0); | 
| assert(validation_data_url_[validation_data_url_.length() - 1] == '/'); | 
| assert(storage_ != NULL); | 
| @@ -66,34 +188,43 @@ void Retriever::Retrieve(const std::string& key, | 
| void Retriever::OnDataRetrievedFromStorage(bool success, | 
| const std::string& key, | 
| const std::string& stored_data) { | 
| - // TODO(rouslan): Add validation for data integrity and freshness. If a | 
| - // download fails, then it's OK to use stale data. | 
| if (success) { | 
| - scoped_ptr<Callback> retrieved = GetCallbackForKey(key); | 
| - if (retrieved != NULL) { | 
| - (*retrieved)(success, key, stored_data); | 
| + std::string unwrapped; | 
| + double age_in_seconds = 0.0; | 
| + if (VerifyAndExtractTimestamp(stored_data, &unwrapped, &age_in_seconds)) { | 
| + if (age_in_seconds < kStaleDataAgeInSeconds) { | 
| + InvokeCallbackForKey(key, success, unwrapped); | 
| + return; | 
| + } else { | 
| 
 
Evan Stade
2014/01/23 23:50:20
no else after return
 
please use gerrit instead
2014/01/23 23:54:56
Done.
 
 | 
| + stale_data_[key] = unwrapped; | 
| + } | 
| } | 
| - } else { | 
| - downloader_->Download(GetUrlForKey(key), | 
| - BuildCallback(this, &Retriever::OnDownloaded)); | 
| } | 
| + | 
| + downloader_->Download(GetUrlForKey(key), | 
| + BuildCallback(this, &Retriever::OnDownloaded)); | 
| } | 
| void Retriever::OnDownloaded(bool success, | 
| const std::string& url, | 
| const std::string& downloaded_data) { | 
| const std::string& key = GetKeyForUrl(url); | 
| - std::string response; | 
| + std::map<std::string, std::string>::iterator stale_data_it = | 
| + stale_data_.find(key); | 
| + | 
| if (success) { | 
| - storage_->Put(key, downloaded_data); | 
| - response = downloaded_data; | 
| + storage_->Put(key, PrependTimestamp(downloaded_data)); | 
| + InvokeCallbackForKey(key, success, downloaded_data); | 
| + } else if (stale_data_it != stale_data_.end()) { | 
| + InvokeCallbackForKey(key, true, stale_data_it->second); | 
| } else { | 
| - success = FallbackDataStore::Get(key, &response); | 
| + std::string fallback; | 
| + success = FallbackDataStore::Get(key, &fallback); | 
| + InvokeCallbackForKey(key, success, fallback); | 
| } | 
| - scoped_ptr<Callback> retrieved = GetCallbackForKey(key); | 
| - if (retrieved != NULL) { | 
| - (*retrieved)(success, key, response); | 
| + if (stale_data_it != stale_data_.end()) { | 
| + stale_data_.erase(stale_data_it); | 
| } | 
| } | 
| @@ -102,10 +233,10 @@ std::string Retriever::GetUrlForKey(const std::string& key) const { | 
| } | 
| std::string Retriever::GetKeyForUrl(const std::string& url) const { | 
| - if (url.compare(0, validation_data_url_.length(), validation_data_url_) == 0) | 
| - return url.substr(validation_data_url_.length()); | 
| - | 
| - return std::string(); | 
| + return | 
| + url.compare(0, validation_data_url_.length(), validation_data_url_) == 0 | 
| + ? url.substr(validation_data_url_.length()) | 
| + : std::string(); | 
| } | 
| bool Retriever::IsValidationDataUrl(const std::string& url) const { | 
| @@ -113,17 +244,21 @@ bool Retriever::IsValidationDataUrl(const std::string& url) const { | 
| url.compare(0, validation_data_url_.length(), validation_data_url_) == 0; | 
| } | 
| -scoped_ptr<Retriever::Callback> Retriever::GetCallbackForKey( | 
| - const std::string& key) { | 
| +void Retriever::InvokeCallbackForKey(const std::string& key, | 
| + bool success, | 
| + const std::string& data) { | 
| std::map<std::string, Callback*>::iterator iter = | 
| requests_.find(key); | 
| if (iter == requests_.end()) { | 
| - // An abandonened request. | 
| - return scoped_ptr<Callback>(); | 
| + // An abandoned request. | 
| + return; | 
| } | 
| scoped_ptr<Callback> callback(iter->second); | 
| requests_.erase(iter); | 
| - return callback.Pass(); | 
| + if (callback == NULL) { | 
| + return; | 
| + } | 
| + (*callback)(success, key, data); | 
| } | 
| } // namespace addressinput |