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