| OLD | NEW |
| (Empty) |
| 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 kHistoryAudioHistoryUrl[] = | |
| 44 "https://history.google.com/history/api/lookup?client=audio"; | |
| 45 | |
| 46 const char kHistoryAudioHistoryChangeUrl[] = | |
| 47 "https://history.google.com/history/api/change"; | |
| 48 | |
| 49 const char kPostDataMimeType[] = "text/plain"; | |
| 50 | |
| 51 // The maximum number of retries for the URLFetcher requests. | |
| 52 const size_t kMaxRetries = 1; | |
| 53 | |
| 54 class RequestImpl : public WebHistoryService::Request, | |
| 55 private OAuth2TokenService::Consumer, | |
| 56 private net::URLFetcherDelegate { | |
| 57 public: | |
| 58 ~RequestImpl() override {} | |
| 59 | |
| 60 // Returns the response code received from the server, which will only be | |
| 61 // valid if the request succeeded. | |
| 62 int GetResponseCode() override { return response_code_; } | |
| 63 | |
| 64 // Returns the contents of the response body received from the server. | |
| 65 const std::string& GetResponseBody() override { return response_body_; } | |
| 66 | |
| 67 bool IsPending() override { return is_pending_; } | |
| 68 | |
| 69 private: | |
| 70 friend class history::WebHistoryService; | |
| 71 | |
| 72 RequestImpl(Profile* profile, | |
| 73 const GURL& url, | |
| 74 const WebHistoryService::CompletionCallback& callback) | |
| 75 : OAuth2TokenService::Consumer("web_history"), | |
| 76 profile_(profile), | |
| 77 url_(url), | |
| 78 response_code_(0), | |
| 79 auth_retry_count_(0), | |
| 80 callback_(callback), | |
| 81 is_pending_(false) { | |
| 82 } | |
| 83 | |
| 84 // Tells the request to do its thang. | |
| 85 void Start() override { | |
| 86 OAuth2TokenService::ScopeSet oauth_scopes; | |
| 87 oauth_scopes.insert(kHistoryOAuthScope); | |
| 88 | |
| 89 ProfileOAuth2TokenService* token_service = | |
| 90 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_); | |
| 91 SigninManagerBase* signin_manager = | |
| 92 SigninManagerFactory::GetForProfile(profile_); | |
| 93 token_request_ = token_service->StartRequest( | |
| 94 signin_manager->GetAuthenticatedAccountId(), oauth_scopes, this); | |
| 95 is_pending_ = true; | |
| 96 } | |
| 97 | |
| 98 // content::URLFetcherDelegate interface. | |
| 99 void OnURLFetchComplete(const net::URLFetcher* source) override { | |
| 100 DCHECK_EQ(source, url_fetcher_.get()); | |
| 101 response_code_ = url_fetcher_->GetResponseCode(); | |
| 102 | |
| 103 UMA_HISTOGRAM_CUSTOM_ENUMERATION("WebHistory.OAuthTokenResponseCode", | |
| 104 net::HttpUtil::MapStatusCodeForHistogram(response_code_), | |
| 105 net::HttpUtil::GetStatusCodesForHistogram()); | |
| 106 | |
| 107 // If the response code indicates that the token might not be valid, | |
| 108 // invalidate the token and try again. | |
| 109 if (response_code_ == net::HTTP_UNAUTHORIZED && ++auth_retry_count_ <= 1) { | |
| 110 OAuth2TokenService::ScopeSet oauth_scopes; | |
| 111 oauth_scopes.insert(kHistoryOAuthScope); | |
| 112 ProfileOAuth2TokenService* token_service = | |
| 113 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_); | |
| 114 SigninManagerBase* signin_manager = | |
| 115 SigninManagerFactory::GetForProfile(profile_); | |
| 116 token_service->InvalidateToken( | |
| 117 signin_manager->GetAuthenticatedAccountId(), | |
| 118 oauth_scopes, | |
| 119 access_token_); | |
| 120 | |
| 121 access_token_.clear(); | |
| 122 Start(); | |
| 123 return; | |
| 124 } | |
| 125 url_fetcher_->GetResponseAsString(&response_body_); | |
| 126 url_fetcher_.reset(); | |
| 127 is_pending_ = false; | |
| 128 callback_.Run(this, true); | |
| 129 // It is valid for the callback to delete |this|, so do not access any | |
| 130 // members below here. | |
| 131 } | |
| 132 | |
| 133 // OAuth2TokenService::Consumer interface. | |
| 134 void OnGetTokenSuccess(const OAuth2TokenService::Request* request, | |
| 135 const std::string& access_token, | |
| 136 const base::Time& expiration_time) override { | |
| 137 token_request_.reset(); | |
| 138 DCHECK(!access_token.empty()); | |
| 139 access_token_ = access_token; | |
| 140 | |
| 141 UMA_HISTOGRAM_BOOLEAN("WebHistory.OAuthTokenCompletion", true); | |
| 142 | |
| 143 // Got an access token -- start the actual API request. | |
| 144 url_fetcher_.reset(CreateUrlFetcher(access_token)); | |
| 145 url_fetcher_->Start(); | |
| 146 } | |
| 147 | |
| 148 void OnGetTokenFailure(const OAuth2TokenService::Request* request, | |
| 149 const GoogleServiceAuthError& error) override { | |
| 150 token_request_.reset(); | |
| 151 is_pending_ = false; | |
| 152 | |
| 153 UMA_HISTOGRAM_BOOLEAN("WebHistory.OAuthTokenCompletion", false); | |
| 154 | |
| 155 callback_.Run(this, false); | |
| 156 // It is valid for the callback to delete |this|, so do not access any | |
| 157 // members below here. | |
| 158 } | |
| 159 | |
| 160 // Helper for creating a new URLFetcher for the API request. | |
| 161 net::URLFetcher* CreateUrlFetcher(const std::string& access_token) { | |
| 162 net::URLFetcher::RequestType request_type = post_data_.empty() ? | |
| 163 net::URLFetcher::GET : net::URLFetcher::POST; | |
| 164 net::URLFetcher* fetcher = net::URLFetcher::Create( | |
| 165 url_, request_type, this); | |
| 166 fetcher->SetRequestContext(profile_->GetRequestContext()); | |
| 167 fetcher->SetMaxRetriesOn5xx(kMaxRetries); | |
| 168 fetcher->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | | |
| 169 net::LOAD_DO_NOT_SAVE_COOKIES); | |
| 170 fetcher->AddExtraRequestHeader("Authorization: Bearer " + access_token); | |
| 171 fetcher->AddExtraRequestHeader("X-Developer-Key: " + | |
| 172 GaiaUrls::GetInstance()->oauth2_chrome_client_id()); | |
| 173 if (request_type == net::URLFetcher::POST) | |
| 174 fetcher->SetUploadData(kPostDataMimeType, post_data_); | |
| 175 return fetcher; | |
| 176 } | |
| 177 | |
| 178 void SetPostData(const std::string& post_data) override { | |
| 179 post_data_ = post_data; | |
| 180 } | |
| 181 | |
| 182 Profile* profile_; | |
| 183 | |
| 184 // The URL of the API endpoint. | |
| 185 GURL url_; | |
| 186 | |
| 187 // POST data to be sent with the request (may be empty). | |
| 188 std::string post_data_; | |
| 189 | |
| 190 // The OAuth2 access token request. | |
| 191 scoped_ptr<OAuth2TokenService::Request> token_request_; | |
| 192 | |
| 193 // The current OAuth2 access token. | |
| 194 std::string access_token_; | |
| 195 | |
| 196 // Handles the actual API requests after the OAuth token is acquired. | |
| 197 scoped_ptr<net::URLFetcher> url_fetcher_; | |
| 198 | |
| 199 // Holds the response code received from the server. | |
| 200 int response_code_; | |
| 201 | |
| 202 // Holds the response body received from the server. | |
| 203 std::string response_body_; | |
| 204 | |
| 205 // The number of times this request has already been retried due to | |
| 206 // authorization problems. | |
| 207 int auth_retry_count_; | |
| 208 | |
| 209 // The callback to execute when the query is complete. | |
| 210 WebHistoryService::CompletionCallback callback_; | |
| 211 | |
| 212 // True if the request was started and has not yet completed, otherwise false. | |
| 213 bool is_pending_; | |
| 214 }; | |
| 215 | |
| 216 // Converts a time into a string for use as a parameter in a request to the | |
| 217 // history server. | |
| 218 std::string ServerTimeString(base::Time time) { | |
| 219 if (time < base::Time::UnixEpoch()) { | |
| 220 return base::Int64ToString(0); | |
| 221 } else { | |
| 222 return base::Int64ToString( | |
| 223 (time - base::Time::UnixEpoch()).InMicroseconds()); | |
| 224 } | |
| 225 } | |
| 226 | |
| 227 // Returns a URL for querying the history server for a query specified by | |
| 228 // |options|. |version_info|, if not empty, should be a token that was received | |
| 229 // from the server in response to a write operation. It is used to help ensure | |
| 230 // read consistency after a write. | |
| 231 GURL GetQueryUrl(const base::string16& text_query, | |
| 232 const QueryOptions& options, | |
| 233 const std::string& version_info) { | |
| 234 GURL url = GURL(kHistoryQueryHistoryUrl); | |
| 235 url = net::AppendQueryParameter(url, "titles", "1"); | |
| 236 | |
| 237 // Take |begin_time|, |end_time|, and |max_count| from the original query | |
| 238 // options, and convert them to the equivalent URL parameters. | |
| 239 | |
| 240 base::Time end_time = | |
| 241 std::min(base::Time::FromInternalValue(options.EffectiveEndTime()), | |
| 242 base::Time::Now()); | |
| 243 url = net::AppendQueryParameter(url, "max", ServerTimeString(end_time)); | |
| 244 | |
| 245 if (!options.begin_time.is_null()) { | |
| 246 url = net::AppendQueryParameter( | |
| 247 url, "min", ServerTimeString(options.begin_time)); | |
| 248 } | |
| 249 | |
| 250 if (options.max_count) { | |
| 251 url = net::AppendQueryParameter( | |
| 252 url, "num", base::IntToString(options.max_count)); | |
| 253 } | |
| 254 | |
| 255 if (!text_query.empty()) | |
| 256 url = net::AppendQueryParameter(url, "q", base::UTF16ToUTF8(text_query)); | |
| 257 | |
| 258 if (!version_info.empty()) | |
| 259 url = net::AppendQueryParameter(url, "kvi", version_info); | |
| 260 | |
| 261 return url; | |
| 262 } | |
| 263 | |
| 264 // Creates a DictionaryValue to hold the parameters for a deletion. | |
| 265 // Ownership is passed to the caller. | |
| 266 // |url| may be empty, indicating a time-range deletion. | |
| 267 base::DictionaryValue* CreateDeletion( | |
| 268 const std::string& min_time, | |
| 269 const std::string& max_time, | |
| 270 const GURL& url) { | |
| 271 base::DictionaryValue* deletion = new base::DictionaryValue; | |
| 272 deletion->SetString("type", "CHROME_HISTORY"); | |
| 273 if (url.is_valid()) | |
| 274 deletion->SetString("url", url.spec()); | |
| 275 deletion->SetString("min_timestamp_usec", min_time); | |
| 276 deletion->SetString("max_timestamp_usec", max_time); | |
| 277 return deletion; | |
| 278 } | |
| 279 | |
| 280 } // namespace | |
| 281 | |
| 282 WebHistoryService::Request::Request() { | |
| 283 } | |
| 284 | |
| 285 WebHistoryService::Request::~Request() { | |
| 286 } | |
| 287 | |
| 288 WebHistoryService::WebHistoryService(Profile* profile) | |
| 289 : profile_(profile), | |
| 290 weak_ptr_factory_(this) { | |
| 291 } | |
| 292 | |
| 293 WebHistoryService::~WebHistoryService() { | |
| 294 STLDeleteElements(&pending_expire_requests_); | |
| 295 STLDeleteElements(&pending_audio_history_requests_); | |
| 296 } | |
| 297 | |
| 298 WebHistoryService::Request* WebHistoryService::CreateRequest( | |
| 299 const GURL& url, | |
| 300 const CompletionCallback& callback) { | |
| 301 return new RequestImpl(profile_, url, callback); | |
| 302 } | |
| 303 | |
| 304 // static | |
| 305 scoped_ptr<base::DictionaryValue> WebHistoryService::ReadResponse( | |
| 306 WebHistoryService::Request* request) { | |
| 307 scoped_ptr<base::DictionaryValue> result; | |
| 308 if (request->GetResponseCode() == net::HTTP_OK) { | |
| 309 base::Value* value = base::JSONReader::Read(request->GetResponseBody()); | |
| 310 if (value && value->IsType(base::Value::TYPE_DICTIONARY)) | |
| 311 result.reset(static_cast<base::DictionaryValue*>(value)); | |
| 312 else | |
| 313 DLOG(WARNING) << "Non-JSON response received from history server."; | |
| 314 } | |
| 315 return result.Pass(); | |
| 316 } | |
| 317 | |
| 318 scoped_ptr<WebHistoryService::Request> WebHistoryService::QueryHistory( | |
| 319 const base::string16& text_query, | |
| 320 const QueryOptions& options, | |
| 321 const WebHistoryService::QueryWebHistoryCallback& callback) { | |
| 322 // Wrap the original callback into a generic completion callback. | |
| 323 CompletionCallback completion_callback = base::Bind( | |
| 324 &WebHistoryService::QueryHistoryCompletionCallback, callback); | |
| 325 | |
| 326 GURL url = GetQueryUrl(text_query, options, server_version_info_); | |
| 327 scoped_ptr<Request> request(CreateRequest(url, completion_callback)); | |
| 328 request->Start(); | |
| 329 return request.Pass(); | |
| 330 } | |
| 331 | |
| 332 void WebHistoryService::ExpireHistory( | |
| 333 const std::vector<ExpireHistoryArgs>& expire_list, | |
| 334 const ExpireWebHistoryCallback& callback) { | |
| 335 base::DictionaryValue delete_request; | |
| 336 scoped_ptr<base::ListValue> deletions(new base::ListValue); | |
| 337 base::Time now = base::Time::Now(); | |
| 338 | |
| 339 for (std::vector<ExpireHistoryArgs>::const_iterator it = expire_list.begin(); | |
| 340 it != expire_list.end(); ++it) { | |
| 341 // Convert the times to server timestamps. | |
| 342 std::string min_timestamp = ServerTimeString(it->begin_time); | |
| 343 // TODO(dubroy): Use sane time (crbug.com/146090) here when it's available. | |
| 344 base::Time end_time = it->end_time; | |
| 345 if (end_time.is_null() || end_time > now) | |
| 346 end_time = now; | |
| 347 std::string max_timestamp = ServerTimeString(end_time); | |
| 348 | |
| 349 for (std::set<GURL>::const_iterator url_iterator = it->urls.begin(); | |
| 350 url_iterator != it->urls.end(); ++url_iterator) { | |
| 351 deletions->Append( | |
| 352 CreateDeletion(min_timestamp, max_timestamp, *url_iterator)); | |
| 353 } | |
| 354 // If no URLs were specified, delete everything in the time range. | |
| 355 if (it->urls.empty()) | |
| 356 deletions->Append(CreateDeletion(min_timestamp, max_timestamp, GURL())); | |
| 357 } | |
| 358 delete_request.Set("del", deletions.release()); | |
| 359 std::string post_data; | |
| 360 base::JSONWriter::Write(&delete_request, &post_data); | |
| 361 | |
| 362 GURL url(kHistoryDeleteHistoryUrl); | |
| 363 | |
| 364 // Append the version info token, if it is available, to help ensure | |
| 365 // consistency with any previous deletions. | |
| 366 if (!server_version_info_.empty()) | |
| 367 url = net::AppendQueryParameter(url, "kvi", server_version_info_); | |
| 368 | |
| 369 // Wrap the original callback into a generic completion callback. | |
| 370 CompletionCallback completion_callback = | |
| 371 base::Bind(&WebHistoryService::ExpireHistoryCompletionCallback, | |
| 372 weak_ptr_factory_.GetWeakPtr(), | |
| 373 callback); | |
| 374 | |
| 375 scoped_ptr<Request> request(CreateRequest(url, completion_callback)); | |
| 376 request->SetPostData(post_data); | |
| 377 request->Start(); | |
| 378 pending_expire_requests_.insert(request.release()); | |
| 379 } | |
| 380 | |
| 381 void WebHistoryService::ExpireHistoryBetween( | |
| 382 const std::set<GURL>& restrict_urls, | |
| 383 base::Time begin_time, | |
| 384 base::Time end_time, | |
| 385 const ExpireWebHistoryCallback& callback) { | |
| 386 std::vector<ExpireHistoryArgs> expire_list(1); | |
| 387 expire_list.back().urls = restrict_urls; | |
| 388 expire_list.back().begin_time = begin_time; | |
| 389 expire_list.back().end_time = end_time; | |
| 390 ExpireHistory(expire_list, callback); | |
| 391 } | |
| 392 | |
| 393 void WebHistoryService::GetAudioHistoryEnabled( | |
| 394 const AudioWebHistoryCallback& callback) { | |
| 395 // Wrap the original callback into a generic completion callback. | |
| 396 CompletionCallback completion_callback = | |
| 397 base::Bind(&WebHistoryService::AudioHistoryCompletionCallback, | |
| 398 weak_ptr_factory_.GetWeakPtr(), | |
| 399 callback); | |
| 400 | |
| 401 GURL url(kHistoryAudioHistoryUrl); | |
| 402 scoped_ptr<Request> request(CreateRequest(url, completion_callback)); | |
| 403 request->Start(); | |
| 404 pending_audio_history_requests_.insert(request.release()); | |
| 405 } | |
| 406 | |
| 407 void WebHistoryService::SetAudioHistoryEnabled( | |
| 408 bool new_enabled_value, | |
| 409 const AudioWebHistoryCallback& callback) { | |
| 410 // Wrap the original callback into a generic completion callback. | |
| 411 CompletionCallback completion_callback = | |
| 412 base::Bind(&WebHistoryService::AudioHistoryCompletionCallback, | |
| 413 weak_ptr_factory_.GetWeakPtr(), | |
| 414 callback); | |
| 415 | |
| 416 GURL url(kHistoryAudioHistoryChangeUrl); | |
| 417 scoped_ptr<Request> request(CreateRequest(url, completion_callback)); | |
| 418 | |
| 419 base::DictionaryValue enable_audio_history; | |
| 420 enable_audio_history.SetBoolean("enable_history_recording", | |
| 421 new_enabled_value); | |
| 422 enable_audio_history.SetString("client", "audio"); | |
| 423 std::string post_data; | |
| 424 base::JSONWriter::Write(&enable_audio_history, &post_data); | |
| 425 request->SetPostData(post_data); | |
| 426 | |
| 427 request->Start(); | |
| 428 pending_audio_history_requests_.insert(request.release()); | |
| 429 } | |
| 430 | |
| 431 size_t WebHistoryService::GetNumberOfPendingAudioHistoryRequests() { | |
| 432 return pending_audio_history_requests_.size(); | |
| 433 } | |
| 434 | |
| 435 // static | |
| 436 void WebHistoryService::QueryHistoryCompletionCallback( | |
| 437 const WebHistoryService::QueryWebHistoryCallback& callback, | |
| 438 WebHistoryService::Request* request, | |
| 439 bool success) { | |
| 440 scoped_ptr<base::DictionaryValue> response_value; | |
| 441 if (success) | |
| 442 response_value = ReadResponse(request); | |
| 443 callback.Run(request, response_value.get()); | |
| 444 } | |
| 445 | |
| 446 void WebHistoryService::ExpireHistoryCompletionCallback( | |
| 447 const WebHistoryService::ExpireWebHistoryCallback& callback, | |
| 448 WebHistoryService::Request* request, | |
| 449 bool success) { | |
| 450 scoped_ptr<base::DictionaryValue> response_value; | |
| 451 if (success) { | |
| 452 response_value = ReadResponse(request); | |
| 453 if (response_value) | |
| 454 response_value->GetString("version_info", &server_version_info_); | |
| 455 } | |
| 456 callback.Run(response_value.get() && success); | |
| 457 // Clean up from pending requests. | |
| 458 pending_expire_requests_.erase(request); | |
| 459 delete request; | |
| 460 } | |
| 461 | |
| 462 void WebHistoryService::AudioHistoryCompletionCallback( | |
| 463 const WebHistoryService::AudioWebHistoryCallback& callback, | |
| 464 WebHistoryService::Request* request, | |
| 465 bool success) { | |
| 466 pending_audio_history_requests_.erase(request); | |
| 467 scoped_ptr<WebHistoryService::Request> request_ptr(request); | |
| 468 | |
| 469 scoped_ptr<base::DictionaryValue> response_value; | |
| 470 bool enabled_value = false; | |
| 471 if (success) { | |
| 472 response_value = ReadResponse(request_ptr.get()); | |
| 473 if (response_value) | |
| 474 response_value->GetBoolean("history_recording_enabled", &enabled_value); | |
| 475 } | |
| 476 callback.Run(success, enabled_value); | |
| 477 } | |
| 478 | |
| 479 } // namespace history | |
| OLD | NEW |