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
30namespace history {
31
32namespace {
33
34const char kHistoryOAuthScope[] =
35    "https://www.googleapis.com/auth/chromesync";
36
37const char kHistoryQueryHistoryUrl[] =
38    "https://history.google.com/history/api/lookup?client=chrome";
39
40const char kHistoryDeleteHistoryUrl[] =
41    "https://history.google.com/history/api/delete?client=chrome";
42
43const char kPostDataMimeType[] = "text/plain";
44
45// The maximum number of retries for the URLFetcher requests.
46const size_t kMaxRetries = 1;
47
48class RequestImpl : public WebHistoryService::Request,
49                    private OAuth2TokenService::Consumer,
50                    private net::URLFetcherDelegate {
51 public:
52  virtual ~RequestImpl() {
53  }
54
55  // Returns the response code received from the server, which will only be
56  // valid if the request succeeded.
57  int response_code() { return response_code_; }
58
59  // Returns the contents of the response body received from the server.
60  const std::string& response_body() { return response_body_; }
61
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
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.
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.
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.
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
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.
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
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.
218scoped_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.
232std::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.
245GURL 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.
281base::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
296WebHistoryService::Request::Request() {
297}
298
299WebHistoryService::Request::~Request() {
300}
301
302WebHistoryService::WebHistoryService(Profile* profile)
303    : profile_(profile),
304      weak_ptr_factory_(this) {
305}
306
307WebHistoryService::~WebHistoryService() {
308  STLDeleteElements(&pending_expire_requests_);
309}
310
311scoped_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
326void 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
376void 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
389void 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
399void 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