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 |