• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/history/web_history_service.h"
6 
7 #include "base/bind.h"
8 #include "base/json/json_reader.h"
9 #include "base/json/json_writer.h"
10 #include "base/metrics/histogram.h"
11 #include "base/stl_util.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/values.h"
15 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
16 #include "chrome/browser/signin/signin_manager_factory.h"
17 #include "components/signin/core/browser/profile_oauth2_token_service.h"
18 #include "components/signin/core/browser/signin_manager.h"
19 #include "google_apis/gaia/gaia_urls.h"
20 #include "google_apis/gaia/google_service_auth_error.h"
21 #include "google_apis/gaia/oauth2_token_service.h"
22 #include "net/base/load_flags.h"
23 #include "net/base/url_util.h"
24 #include "net/http/http_status_code.h"
25 #include "net/http/http_util.h"
26 #include "net/url_request/url_fetcher.h"
27 #include "net/url_request/url_fetcher_delegate.h"
28 #include "url/gurl.h"
29 
30 namespace history {
31 
32 namespace {
33 
34 const char kHistoryOAuthScope[] =
35     "https://www.googleapis.com/auth/chromesync";
36 
37 const char kHistoryQueryHistoryUrl[] =
38     "https://history.google.com/history/api/lookup?client=chrome";
39 
40 const char kHistoryDeleteHistoryUrl[] =
41     "https://history.google.com/history/api/delete?client=chrome";
42 
43 const char kPostDataMimeType[] = "text/plain";
44 
45 // The maximum number of retries for the URLFetcher requests.
46 const size_t kMaxRetries = 1;
47 
48 class RequestImpl : public WebHistoryService::Request,
49                     private OAuth2TokenService::Consumer,
50                     private net::URLFetcherDelegate {
51  public:
~RequestImpl()52   virtual ~RequestImpl() {
53   }
54 
55   // Returns the response code received from the server, which will only be
56   // valid if the request succeeded.
response_code()57   int response_code() { return response_code_; }
58 
59   // Returns the contents of the response body received from the server.
response_body()60   const std::string& response_body() { return response_body_; }
61 
is_pending()62   virtual bool is_pending() OVERRIDE { return is_pending_; }
63 
64  private:
65   friend class history::WebHistoryService;
66 
67   typedef base::Callback<void(Request*, bool)> CompletionCallback;
68 
RequestImpl(Profile * profile,const GURL & url,const CompletionCallback & callback)69   RequestImpl(Profile* profile,
70               const GURL& url,
71               const CompletionCallback& callback)
72       : OAuth2TokenService::Consumer("web_history"),
73         profile_(profile),
74         url_(url),
75         response_code_(0),
76         auth_retry_count_(0),
77         callback_(callback),
78         is_pending_(false) {
79   }
80 
81   // Tells the request to do its thang.
Start()82   void Start() {
83     OAuth2TokenService::ScopeSet oauth_scopes;
84     oauth_scopes.insert(kHistoryOAuthScope);
85 
86     ProfileOAuth2TokenService* token_service =
87         ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
88     SigninManagerBase* signin_manager =
89         SigninManagerFactory::GetForProfile(profile_);
90     token_request_ = token_service->StartRequest(
91         signin_manager->GetAuthenticatedAccountId(), oauth_scopes, this);
92     is_pending_ = true;
93   }
94 
95   // content::URLFetcherDelegate interface.
OnURLFetchComplete(const net::URLFetcher * source)96   virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE {
97     DCHECK_EQ(source, url_fetcher_.get());
98     response_code_ = url_fetcher_->GetResponseCode();
99 
100     UMA_HISTOGRAM_CUSTOM_ENUMERATION("WebHistory.OAuthTokenResponseCode",
101         net::HttpUtil::MapStatusCodeForHistogram(response_code_),
102         net::HttpUtil::GetStatusCodesForHistogram());
103 
104     // If the response code indicates that the token might not be valid,
105     // invalidate the token and try again.
106     if (response_code_ == net::HTTP_UNAUTHORIZED && ++auth_retry_count_ <= 1) {
107       OAuth2TokenService::ScopeSet oauth_scopes;
108       oauth_scopes.insert(kHistoryOAuthScope);
109       ProfileOAuth2TokenService* token_service =
110           ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
111       SigninManagerBase* signin_manager =
112           SigninManagerFactory::GetForProfile(profile_);
113       token_service->InvalidateToken(
114           signin_manager->GetAuthenticatedAccountId(),
115           oauth_scopes,
116           access_token_);
117 
118       access_token_.clear();
119       Start();
120       return;
121     }
122     url_fetcher_->GetResponseAsString(&response_body_);
123     url_fetcher_.reset();
124     is_pending_ = false;
125     callback_.Run(this, true);
126     // It is valid for the callback to delete |this|, so do not access any
127     // members below here.
128   }
129 
130   // OAuth2TokenService::Consumer interface.
OnGetTokenSuccess(const OAuth2TokenService::Request * request,const std::string & access_token,const base::Time & expiration_time)131   virtual void OnGetTokenSuccess(
132       const OAuth2TokenService::Request* request,
133       const std::string& access_token,
134       const base::Time& expiration_time) OVERRIDE {
135     token_request_.reset();
136     DCHECK(!access_token.empty());
137     access_token_ = access_token;
138 
139     UMA_HISTOGRAM_BOOLEAN("WebHistory.OAuthTokenCompletion", true);
140 
141     // Got an access token -- start the actual API request.
142     url_fetcher_.reset(CreateUrlFetcher(access_token));
143     url_fetcher_->Start();
144   }
145 
OnGetTokenFailure(const OAuth2TokenService::Request * request,const GoogleServiceAuthError & error)146   virtual void OnGetTokenFailure(
147       const OAuth2TokenService::Request* request,
148       const GoogleServiceAuthError& error) OVERRIDE {
149     token_request_.reset();
150     is_pending_ = false;
151 
152     UMA_HISTOGRAM_BOOLEAN("WebHistory.OAuthTokenCompletion", false);
153 
154     callback_.Run(this, false);
155     // It is valid for the callback to delete |this|, so do not access any
156     // members below here.
157   }
158 
159   // Helper for creating a new URLFetcher for the API request.
CreateUrlFetcher(const std::string & access_token)160   net::URLFetcher* CreateUrlFetcher(const std::string& access_token) {
161     net::URLFetcher::RequestType request_type = post_data_.empty() ?
162         net::URLFetcher::GET : net::URLFetcher::POST;
163     net::URLFetcher* fetcher = net::URLFetcher::Create(
164         url_, request_type, this);
165     fetcher->SetRequestContext(profile_->GetRequestContext());
166     fetcher->SetMaxRetriesOn5xx(kMaxRetries);
167     fetcher->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
168                           net::LOAD_DO_NOT_SAVE_COOKIES);
169     fetcher->AddExtraRequestHeader("Authorization: Bearer " + access_token);
170     fetcher->AddExtraRequestHeader("X-Developer-Key: " +
171         GaiaUrls::GetInstance()->oauth2_chrome_client_id());
172     if (request_type == net::URLFetcher::POST)
173       fetcher->SetUploadData(kPostDataMimeType, post_data_);
174     return fetcher;
175   }
176 
set_post_data(const std::string & post_data)177   void set_post_data(const std::string& post_data) {
178     post_data_ = post_data;
179   }
180 
181   Profile* profile_;
182 
183   // The URL of the API endpoint.
184   GURL url_;
185 
186   // POST data to be sent with the request (may be empty).
187   std::string post_data_;
188 
189   // The OAuth2 access token request.
190   scoped_ptr<OAuth2TokenService::Request> token_request_;
191 
192   // The current OAuth2 access token.
193   std::string access_token_;
194 
195   // Handles the actual API requests after the OAuth token is acquired.
196   scoped_ptr<net::URLFetcher> url_fetcher_;
197 
198   // Holds the response code received from the server.
199   int response_code_;
200 
201   // Holds the response body received from the server.
202   std::string response_body_;
203 
204   // The number of times this request has already been retried due to
205   // authorization problems.
206   int auth_retry_count_;
207 
208   // The callback to execute when the query is complete.
209   CompletionCallback callback_;
210 
211   // True if the request was started and has not yet completed, otherwise false.
212   bool is_pending_;
213 };
214 
215 // Extracts a JSON-encoded HTTP response into a DictionaryValue.
216 // If |request|'s HTTP response code indicates failure, or if the response
217 // body is not JSON, a null pointer is returned.
ReadResponse(RequestImpl * request)218 scoped_ptr<base::DictionaryValue> ReadResponse(RequestImpl* request) {
219   scoped_ptr<base::DictionaryValue> result;
220   if (request->response_code() == net::HTTP_OK) {
221     base::Value* value = base::JSONReader::Read(request->response_body());
222     if (value && value->IsType(base::Value::TYPE_DICTIONARY))
223       result.reset(static_cast<base::DictionaryValue*>(value));
224     else
225       DLOG(WARNING) << "Non-JSON response received from history server.";
226   }
227   return result.Pass();
228 }
229 
230 // Converts a time into a string for use as a parameter in a request to the
231 // history server.
ServerTimeString(base::Time time)232 std::string ServerTimeString(base::Time time) {
233   if (time < base::Time::UnixEpoch()) {
234     return base::Int64ToString(0);
235   } else {
236     return base::Int64ToString(
237         (time - base::Time::UnixEpoch()).InMicroseconds());
238   }
239 }
240 
241 // Returns a URL for querying the history server for a query specified by
242 // |options|. |version_info|, if not empty, should be a token that was received
243 // from the server in response to a write operation. It is used to help ensure
244 // read consistency after a write.
GetQueryUrl(const base::string16 & text_query,const QueryOptions & options,const std::string & version_info)245 GURL GetQueryUrl(const base::string16& text_query,
246                  const QueryOptions& options,
247                  const std::string& version_info) {
248   GURL url = GURL(kHistoryQueryHistoryUrl);
249   url = net::AppendQueryParameter(url, "titles", "1");
250 
251   // Take |begin_time|, |end_time|, and |max_count| from the original query
252   // options, and convert them to the equivalent URL parameters.
253 
254   base::Time end_time =
255       std::min(base::Time::FromInternalValue(options.EffectiveEndTime()),
256                base::Time::Now());
257   url = net::AppendQueryParameter(url, "max", ServerTimeString(end_time));
258 
259   if (!options.begin_time.is_null()) {
260     url = net::AppendQueryParameter(
261         url, "min", ServerTimeString(options.begin_time));
262   }
263 
264   if (options.max_count) {
265     url = net::AppendQueryParameter(
266         url, "num", base::IntToString(options.max_count));
267   }
268 
269   if (!text_query.empty())
270     url = net::AppendQueryParameter(url, "q", base::UTF16ToUTF8(text_query));
271 
272   if (!version_info.empty())
273     url = net::AppendQueryParameter(url, "kvi", version_info);
274 
275   return url;
276 }
277 
278 // Creates a DictionaryValue to hold the parameters for a deletion.
279 // Ownership is passed to the caller.
280 // |url| may be empty, indicating a time-range deletion.
CreateDeletion(const std::string & min_time,const std::string & max_time,const GURL & url)281 base::DictionaryValue* CreateDeletion(
282     const std::string& min_time,
283     const std::string& max_time,
284     const GURL& url) {
285   base::DictionaryValue* deletion = new base::DictionaryValue;
286   deletion->SetString("type", "CHROME_HISTORY");
287   if (url.is_valid())
288     deletion->SetString("url", url.spec());
289   deletion->SetString("min_timestamp_usec", min_time);
290   deletion->SetString("max_timestamp_usec", max_time);
291   return deletion;
292 }
293 
294 }  // namespace
295 
Request()296 WebHistoryService::Request::Request() {
297 }
298 
~Request()299 WebHistoryService::Request::~Request() {
300 }
301 
WebHistoryService(Profile * profile)302 WebHistoryService::WebHistoryService(Profile* profile)
303     : profile_(profile),
304       weak_ptr_factory_(this) {
305 }
306 
~WebHistoryService()307 WebHistoryService::~WebHistoryService() {
308   STLDeleteElements(&pending_expire_requests_);
309 }
310 
QueryHistory(const base::string16 & text_query,const QueryOptions & options,const WebHistoryService::QueryWebHistoryCallback & callback)311 scoped_ptr<WebHistoryService::Request> WebHistoryService::QueryHistory(
312     const base::string16& text_query,
313     const QueryOptions& options,
314     const WebHistoryService::QueryWebHistoryCallback& callback) {
315   // Wrap the original callback into a generic completion callback.
316   RequestImpl::CompletionCallback completion_callback = base::Bind(
317       &WebHistoryService::QueryHistoryCompletionCallback, callback);
318 
319   GURL url = GetQueryUrl(text_query, options, server_version_info_);
320   scoped_ptr<RequestImpl> request(
321       new RequestImpl(profile_, url, completion_callback));
322   request->Start();
323   return request.PassAs<Request>();
324 }
325 
ExpireHistory(const std::vector<ExpireHistoryArgs> & expire_list,const ExpireWebHistoryCallback & callback)326 void WebHistoryService::ExpireHistory(
327     const std::vector<ExpireHistoryArgs>& expire_list,
328     const ExpireWebHistoryCallback& callback) {
329   base::DictionaryValue delete_request;
330   scoped_ptr<base::ListValue> deletions(new base::ListValue);
331   base::Time now = base::Time::Now();
332 
333   for (std::vector<ExpireHistoryArgs>::const_iterator it = expire_list.begin();
334        it != expire_list.end(); ++it) {
335     // Convert the times to server timestamps.
336     std::string min_timestamp = ServerTimeString(it->begin_time);
337     // TODO(dubroy): Use sane time (crbug.com/146090) here when it's available.
338     base::Time end_time = it->end_time;
339     if (end_time.is_null() || end_time > now)
340       end_time = now;
341     std::string max_timestamp = ServerTimeString(end_time);
342 
343     for (std::set<GURL>::const_iterator url_iterator = it->urls.begin();
344          url_iterator != it->urls.end(); ++url_iterator) {
345       deletions->Append(
346           CreateDeletion(min_timestamp, max_timestamp, *url_iterator));
347     }
348     // If no URLs were specified, delete everything in the time range.
349     if (it->urls.empty())
350       deletions->Append(CreateDeletion(min_timestamp, max_timestamp, GURL()));
351   }
352   delete_request.Set("del", deletions.release());
353   std::string post_data;
354   base::JSONWriter::Write(&delete_request, &post_data);
355 
356   GURL url(kHistoryDeleteHistoryUrl);
357 
358   // Append the version info token, if it is available, to help ensure
359   // consistency with any previous deletions.
360   if (!server_version_info_.empty())
361     url = net::AppendQueryParameter(url, "kvi", server_version_info_);
362 
363   // Wrap the original callback into a generic completion callback.
364   RequestImpl::CompletionCallback completion_callback =
365       base::Bind(&WebHistoryService::ExpireHistoryCompletionCallback,
366                  weak_ptr_factory_.GetWeakPtr(),
367                  callback);
368 
369   scoped_ptr<RequestImpl> request(
370       new RequestImpl(profile_, url, completion_callback));
371   request->set_post_data(post_data);
372   request->Start();
373   pending_expire_requests_.insert(request.release());
374 }
375 
ExpireHistoryBetween(const std::set<GURL> & restrict_urls,base::Time begin_time,base::Time end_time,const ExpireWebHistoryCallback & callback)376 void WebHistoryService::ExpireHistoryBetween(
377     const std::set<GURL>& restrict_urls,
378     base::Time begin_time,
379     base::Time end_time,
380     const ExpireWebHistoryCallback& callback) {
381   std::vector<ExpireHistoryArgs> expire_list(1);
382   expire_list.back().urls = restrict_urls;
383   expire_list.back().begin_time = begin_time;
384   expire_list.back().end_time = end_time;
385   ExpireHistory(expire_list, callback);
386 }
387 
388 // static
QueryHistoryCompletionCallback(const WebHistoryService::QueryWebHistoryCallback & callback,WebHistoryService::Request * request,bool success)389 void WebHistoryService::QueryHistoryCompletionCallback(
390     const WebHistoryService::QueryWebHistoryCallback& callback,
391     WebHistoryService::Request* request,
392     bool success) {
393   scoped_ptr<base::DictionaryValue> response_value;
394   if (success)
395     response_value = ReadResponse(static_cast<RequestImpl*>(request));
396   callback.Run(request, response_value.get());
397 }
398 
ExpireHistoryCompletionCallback(const WebHistoryService::ExpireWebHistoryCallback & callback,WebHistoryService::Request * request,bool success)399 void WebHistoryService::ExpireHistoryCompletionCallback(
400     const WebHistoryService::ExpireWebHistoryCallback& callback,
401     WebHistoryService::Request* request,
402     bool success) {
403   scoped_ptr<base::DictionaryValue> response_value;
404   if (success) {
405     response_value = ReadResponse(static_cast<RequestImpl*>(request));
406     if (response_value)
407       response_value->GetString("version_info", &server_version_info_);
408   }
409   callback.Run(response_value.get() && success);
410   // Clean up from pending requests.
411   pending_expire_requests_.erase(request);
412   delete request;
413 }
414 
415 }  // namespace history
416