Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(58)

Unified Diff: third_party/libaddressinput/chromium/cpp/src/retriever.cc

Issue 144353002: [rac] Use stale libaddressinput data if download fails (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Address comments. Created 6 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..a767a443a734e0cc2da2a5f9a0374b92cf21efec 100644
--- a/third_party/libaddressinput/chromium/cpp/src/retriever.cc
+++ b/third_party/libaddressinput/chromium/cpp/src/retriever.cc
@@ -22,22 +22,141 @@
#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';
+
+// Places the header value into |header_value| parameter and erases the header
+// from |data|. Returns |true| if the header format is valid.
+bool UnwrapHeader(const char* header_prefix,
Evan Stade 2014/01/23 01:44:50 nit: put this about Unwrap also, s/UnwrapHeader/E
please use gerrit instead 2014/01/23 23:11:05 Done.
+ size_t header_prefix_length,
+ std::string* data,
+ std::string* header_value) {
+ assert(header_prefix != NULL);
+ assert(data != NULL);
+ assert(header_value != NULL);
+
+ if (data->compare(
+ 0, header_prefix_length, header_prefix, header_prefix_length) != 0) {
+ return false;
+ }
+
+ std::string::size_type separator_position =
+ data->find(kSeparator, header_prefix_length);
+ if (separator_position == std::string::npos) {
+ return false;
+ }
+
+ header_value->assign(
+ *data, header_prefix_length, separator_position - header_prefix_length);
+ data->erase(0, separator_position + 1);
+
+ return true;
+}
+
+// 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 Wrap(const std::string& data) {
Evan Stade 2014/01/23 01:44:50 s/Wrap/PrependTimestamp
please use gerrit instead 2014/01/23 23:11:05 Done.
+ 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;
+}
+
+// Strips out the timestamp and checksum from |data|. Validates the checksum.
+// 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 |data| is correctly formatted and has the correct
+// checksum.
+bool Unwrap(std::string* data, double* age_in_seconds) {
Evan Stade 2014/01/23 01:44:50 I'd prefer if there were separate in and out param
Evan Stade 2014/01/23 01:44:50 s/Unwrap/VerifyAndExtractTimestamp
please use gerrit instead 2014/01/23 23:11:05 Done.
please use gerrit instead 2014/01/23 23:11:05 Done.
+ assert(data != NULL);
+ assert(age_in_seconds != NULL);
+
+ std::string timestamp_string;
+ if (!UnwrapHeader(
+ kTimestampPrefix, kTimestampPrefixLength, data, &timestamp_string)) {
+ 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 (!UnwrapHeader(kChecksumPrefix, kChecksumPrefixLength, data, &checksum)) {
+ 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,14 +185,18 @@ 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) {
+ std::string unwrapped = stored_data;
+ double age_in_seconds = 0.0;
+ bool valid_format = Unwrap(&unwrapped, &age_in_seconds);
+ if (success && valid_format && age_in_seconds < kStaleDataAgeInSeconds) {
Evan Stade 2014/01/23 01:44:50 if (!success) you shouldn't call Unwrap
please use gerrit instead 2014/01/23 23:11:05 Done.
scoped_ptr<Callback> retrieved = GetCallbackForKey(key);
if (retrieved != NULL) {
- (*retrieved)(success, key, stored_data);
+ (*retrieved)(success, key, unwrapped);
}
} else {
Evan Stade 2014/01/23 01:44:50 nit: prefer early return over else {} that goes to
please use gerrit instead 2014/01/23 23:11:05 Done.
+ if (success && valid_format) {
+ stale_data_[key] = unwrapped;
+ }
downloader_->Download(GetUrlForKey(key),
BuildCallback(this, &Retriever::OnDownloaded));
}
@@ -84,13 +207,23 @@ void Retriever::OnDownloaded(bool success,
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);
+ storage_->Put(key, Wrap(downloaded_data));
response = downloaded_data;
Evan Stade 2014/01/23 01:44:50 unnecessary copy (potentially quite large)
please use gerrit instead 2014/01/23 23:11:05 Done.
+ } else if (stale_data_it != stale_data_.end()) {
+ success = true;
+ response = stale_data_it->second;
Evan Stade 2014/01/23 01:44:50 unnecessary copy (potentially quite large)
please use gerrit instead 2014/01/23 23:11:05 Done.
} else {
success = FallbackDataStore::Get(key, &response);
}
+ if (stale_data_it != stale_data_.end()) {
+ stale_data_.erase(stale_data_it);
+ }
+
scoped_ptr<Callback> retrieved = GetCallbackForKey(key);
if (retrieved != NULL) {
(*retrieved)(success, key, response);

Powered by Google App Engine
This is Rietveld 408576698