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