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/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:
     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.
    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.
    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.
    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.
    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 
    296 WebHistoryService::Request::Request() {
    297 }
    298 
    299 WebHistoryService::Request::~Request() {
    300 }
    301 
    302 WebHistoryService::WebHistoryService(Profile* profile)
    303     : profile_(profile),
    304       weak_ptr_factory_(this) {
    305 }
    306 
    307 WebHistoryService::~WebHistoryService() {
    308   STLDeleteElements(&pending_expire_requests_);
    309 }
    310 
    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 
    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 
    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
    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 
    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