• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (C) 2013 Google Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "retriever.h"
16 
17 #include <libaddressinput/callback.h>
18 #include <libaddressinput/downloader.h>
19 #include <libaddressinput/storage.h>
20 #include <libaddressinput/util/basictypes.h>
21 #include <libaddressinput/util/scoped_ptr.h>
22 
23 #include <cassert>
24 #include <cstddef>
25 #include <cstdlib>
26 #include <ctime>
27 #include <map>
28 #include <string>
29 
30 #include "fallback_data_store.h"
31 #include "util/md5.h"
32 #include "util/stl_util.h"
33 #include "util/string_util.h"
34 
35 namespace i18n {
36 namespace addressinput {
37 
38 namespace {
39 
40 // The number of seconds after which data is considered stale. The staleness
41 // threshold is 30 days:
42 //    30 days *
43 //    24 hours per day *
44 //    60 minutes per hour *
45 //    60 seconds per minute.
46 static const double kStaleDataAgeInSeconds = 30.0 * 24.0 * 60.0 * 60.0;
47 
48 // The prefix for the timestamp line in the footer.
49 const char kTimestampPrefix[] = "timestamp=";
50 
51 // The prefix for the checksum line in the footer.
52 const char kChecksumPrefix[] = "checksum=";
53 
54 // The separator between lines of footer and data.
55 const char kSeparator = '\n';
56 
57 // Returns |data| with attached checksum and current timestamp. Format:
58 //
59 //    <data>
60 //    checksum=<checksum>
61 //    timestamp=<timestamp>
62 //
63 // The timestamp is the time_t that was returned from time(NULL) function. The
64 // timestamp does not need to be portable because it is written and read only by
65 // Retriever. The value is somewhat human-readable: it is the number of seconds
66 // since the epoch.
67 //
68 // The checksum is the 32-character hexadecimal MD5 checksum of <data>. It is
69 // meant to protect from random file changes on disk.
AppendTimestamp(std::string * data)70 void AppendTimestamp(std::string* data) {
71   std::string md5 = MD5String(*data);
72 
73   data->push_back(kSeparator);
74   data->append(kChecksumPrefix);
75   data->append(md5);
76 
77   data->push_back(kSeparator);
78   data->append(kTimestampPrefix);
79   data->append(TimeToString(time(NULL)));
80 }
81 
82 // Places the footer value into |footer_value| parameter and the rest of the
83 // data into |data| parameter. Returns |true| if the footer format is valid.
ExtractFooter(scoped_ptr<std::string> data_and_footer,const std::string & footer_prefix,std::string * footer_value,scoped_ptr<std::string> * data)84 bool ExtractFooter(scoped_ptr<std::string> data_and_footer,
85                    const std::string& footer_prefix,
86                    std::string* footer_value,
87                    scoped_ptr<std::string>* data) {
88   assert(footer_value != NULL);
89   assert(data != NULL);
90 
91   std::string::size_type separator_position =
92       data_and_footer->rfind(kSeparator);
93   if (separator_position == std::string::npos) {
94     return false;
95   }
96 
97   std::string::size_type footer_start = separator_position + 1;
98   if (data_and_footer->compare(footer_start,
99                                footer_prefix.length(),
100                                footer_prefix) != 0) {
101     return false;
102   }
103 
104   *footer_value =
105       data_and_footer->substr(footer_start + footer_prefix.length());
106   *data = data_and_footer.Pass();
107   (*data)->resize(separator_position);
108   return true;
109 }
110 
111 // Strips out the timestamp and checksum from |data_and_footer|. Validates the
112 // checksum. Saves the footer-less data into |data|. Compares the parsed
113 // timestamp with current time and saves the difference into |age_in_seconds|.
114 //
115 // The parameters should not be NULL.
116 //
117 // Returns |true| if |data_and_footer| is correctly formatted and has the
118 // correct checksum.
VerifyAndExtractTimestamp(const std::string & data_and_footer,scoped_ptr<std::string> * data,double * age_in_seconds)119 bool VerifyAndExtractTimestamp(const std::string& data_and_footer,
120                                scoped_ptr<std::string>* data,
121                                double* age_in_seconds) {
122   assert(data != NULL);
123   assert(age_in_seconds != NULL);
124 
125   std::string timestamp_string;
126   scoped_ptr<std::string> checksum_and_data;
127   if (!ExtractFooter(make_scoped_ptr(new std::string(data_and_footer)),
128                      kTimestampPrefix, &timestamp_string, &checksum_and_data)) {
129     return false;
130   }
131 
132   time_t timestamp = atol(timestamp_string.c_str());
133   if (timestamp < 0) {
134     return false;
135   }
136 
137   *age_in_seconds = difftime(time(NULL), timestamp);
138   if (*age_in_seconds < 0.0) {
139     return false;
140   }
141 
142   std::string checksum;
143   if (!ExtractFooter(checksum_and_data.Pass(),
144                      kChecksumPrefix, &checksum, data)) {
145     return false;
146   }
147 
148   return checksum == MD5String(**data);
149 }
150 
151 }  // namespace
152 
Retriever(const std::string & validation_data_url,scoped_ptr<Downloader> downloader,scoped_ptr<Storage> storage)153 Retriever::Retriever(const std::string& validation_data_url,
154                      scoped_ptr<Downloader> downloader,
155                      scoped_ptr<Storage> storage)
156     : validation_data_url_(validation_data_url),
157       downloader_(downloader.Pass()),
158       storage_(storage.Pass()),
159       stale_data_() {
160   assert(validation_data_url_.length() > 0);
161   assert(validation_data_url_[validation_data_url_.length() - 1] == '/');
162   assert(storage_ != NULL);
163   assert(downloader_ != NULL);
164 }
165 
~Retriever()166 Retriever::~Retriever() {
167   STLDeleteValues(&requests_);
168 }
169 
Retrieve(const std::string & key,scoped_ptr<Callback> retrieved)170 void Retriever::Retrieve(const std::string& key,
171                          scoped_ptr<Callback> retrieved) {
172   std::map<std::string, Callback*>::iterator request_it =
173       requests_.find(key);
174   if (request_it != requests_.end()) {
175     // Abandon a previous request.
176     delete request_it->second;
177     requests_.erase(request_it);
178   }
179 
180   requests_[key] = retrieved.release();
181   storage_->Get(key,
182                 BuildCallback(this, &Retriever::OnDataRetrievedFromStorage));
183 }
184 
OnDataRetrievedFromStorage(bool success,const std::string & key,const std::string & stored_data)185 void Retriever::OnDataRetrievedFromStorage(bool success,
186                                            const std::string& key,
187                                            const std::string& stored_data) {
188   scoped_ptr<std::string> unwrapped;
189   double age_in_seconds = 0.0;
190   if (success &&
191       VerifyAndExtractTimestamp(stored_data, &unwrapped, &age_in_seconds)) {
192     if (age_in_seconds < kStaleDataAgeInSeconds) {
193       if (InvokeCallbackForKey(key, success, *unwrapped)) {
194         return;
195       }
196     } else {
197       stale_data_[key].swap(*unwrapped);
198     }
199   }
200 
201   downloader_->Download(GetUrlForKey(key),
202                         BuildScopedPtrCallback(this, &Retriever::OnDownloaded));
203 }
204 
OnDownloaded(bool success,const std::string & url,scoped_ptr<std::string> downloaded_data)205 void Retriever::OnDownloaded(bool success,
206                              const std::string& url,
207                              scoped_ptr<std::string> downloaded_data) {
208   const std::string& key = GetKeyForUrl(url);
209   std::map<std::string, std::string>::iterator stale_data_it =
210       stale_data_.find(key);
211 
212   // This variable tracks whether the client "likes" the data we return. For
213   // example, it could be corrupt --- in this case, we won't place it in
214   // storage.
215   bool data_is_good = false;
216   if (success) {
217     data_is_good = InvokeCallbackForKey(key, success, *downloaded_data);
218     if (data_is_good) {
219       AppendTimestamp(downloaded_data.get());
220       storage_->Put(key, downloaded_data.Pass());
221     }
222   } else if (stale_data_it != stale_data_.end()) {
223     data_is_good = InvokeCallbackForKey(key, true, stale_data_it->second);
224   }
225 
226   if (!success || !data_is_good) {
227     std::string fallback;
228     success = FallbackDataStore::Get(key, &fallback);
229     InvokeCallbackForKey(key, success, fallback);
230   }
231 
232   if (stale_data_it != stale_data_.end()) {
233     stale_data_.erase(stale_data_it);
234   }
235 }
236 
GetUrlForKey(const std::string & key) const237 std::string Retriever::GetUrlForKey(const std::string& key) const {
238   return validation_data_url_ + key;
239 }
240 
GetKeyForUrl(const std::string & url) const241 std::string Retriever::GetKeyForUrl(const std::string& url) const {
242   return
243       url.compare(0, validation_data_url_.length(), validation_data_url_) == 0
244           ? url.substr(validation_data_url_.length())
245           : std::string();
246 }
247 
IsValidationDataUrl(const std::string & url) const248 bool Retriever::IsValidationDataUrl(const std::string& url) const {
249   return
250       url.compare(0, validation_data_url_.length(), validation_data_url_) == 0;
251 }
252 
InvokeCallbackForKey(const std::string & key,bool success,const std::string & data)253 bool Retriever::InvokeCallbackForKey(const std::string& key,
254                                      bool success,
255                                      const std::string& data) {
256   std::map<std::string, Callback*>::iterator iter =
257       requests_.find(key);
258   if (iter == requests_.end()) {
259     // An abandoned request.
260     return true;
261   }
262 
263   scoped_ptr<Callback> callback(iter->second);
264   // If the data is no good, put the request back.
265   if (callback != NULL && !(*callback)(success, key, data)) {
266     requests_[key] = callback.release();
267     return false;
268   }
269 
270   requests_.erase(iter);
271   return true;
272 }
273 
274 }  // namespace addressinput
275 }  // namespace i18n
276