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