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