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