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.
5 #include "chrome/browser/history/web_history_service.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.h"
16 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
17 #include "chrome/browser/signin/signin_manager.h"
18 #include "chrome/browser/signin/signin_manager_factory.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"
34 const char kHistoryOAuthScope[] =
35 "https://www.googleapis.com/auth/chromesync";
37 const char kHistoryQueryHistoryUrl[] =
38 "https://history.google.com/history/api/lookup?client=chrome";
40 const char kHistoryDeleteHistoryUrl[] =
41 "https://history.google.com/history/api/delete?client=chrome";
43 const char kPostDataMimeType[] = "text/plain";
45 // The maximum number of retries for the URLFetcher requests.
46 const size_t kMaxRetries = 1;
48 class RequestImpl : public WebHistoryService::Request,
49 private OAuth2TokenService::Consumer,
50 private net::URLFetcherDelegate {
52 virtual ~RequestImpl() {
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_; }
59 // Returns the contents of the response body received from the server.
60 const std::string& response_body() { return response_body_; }
62 virtual bool is_pending() OVERRIDE { return is_pending_; }
65 friend class history::WebHistoryService;
67 typedef base::Callback<void(Request*, bool)> CompletionCallback;
69 RequestImpl(Profile* profile,
71 const CompletionCallback& callback)
72 : OAuth2TokenService::Consumer("web_history"),
81 // Tells the request to do its thang.
83 OAuth2TokenService::ScopeSet oauth_scopes;
84 oauth_scopes.insert(kHistoryOAuthScope);
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);
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();
100 UMA_HISTOGRAM_CUSTOM_ENUMERATION("WebHistory.OAuthTokenResponseCode",
101 net::HttpUtil::MapStatusCodeForHistogram(response_code_),
102 net::HttpUtil::GetStatusCodesForHistogram());
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(),
118 access_token_.clear();
122 url_fetcher_->GetResponseAsString(&response_body_);
123 url_fetcher_.reset();
125 callback_.Run(this, true);
126 // It is valid for the callback to delete |this|, so do not access any
127 // members below here.
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;
139 UMA_HISTOGRAM_BOOLEAN("WebHistory.OAuthTokenCompletion", true);
141 // Got an access token -- start the actual API request.
142 url_fetcher_.reset(CreateUrlFetcher(access_token));
143 url_fetcher_->Start();
146 virtual void OnGetTokenFailure(
147 const OAuth2TokenService::Request* request,
148 const GoogleServiceAuthError& error) OVERRIDE {
149 token_request_.reset();
152 UMA_HISTOGRAM_BOOLEAN("WebHistory.OAuthTokenCompletion", false);
154 callback_.Run(this, false);
155 // It is valid for the callback to delete |this|, so do not access any
156 // members below here.
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_);
177 void set_post_data(const std::string& post_data) {
178 post_data_ = post_data;
183 // The URL of the API endpoint.
186 // POST data to be sent with the request (may be empty).
187 std::string post_data_;
189 // The OAuth2 access token request.
190 scoped_ptr<OAuth2TokenService::Request> token_request_;
192 // The current OAuth2 access token.
193 std::string access_token_;
195 // Handles the actual API requests after the OAuth token is acquired.
196 scoped_ptr<net::URLFetcher> url_fetcher_;
198 // Holds the response code received from the server.
201 // Holds the response body received from the server.
202 std::string response_body_;
204 // The number of times this request has already been retried due to
205 // authorization problems.
206 int auth_retry_count_;
208 // The callback to execute when the query is complete.
209 CompletionCallback callback_;
211 // True if the request was started and has not yet completed, otherwise false.
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));
225 DLOG(WARNING) << "Non-JSON response received from history server.";
227 return result.Pass();
230 // Converts a time into a string for use as a parameter in a request to the
232 std::string ServerTimeString(base::Time time) {
233 if (time < base::Time::UnixEpoch()) {
234 return base::Int64ToString(0);
236 return base::Int64ToString(
237 (time - base::Time::UnixEpoch()).InMicroseconds());
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");
251 // Take |begin_time|, |end_time|, and |max_count| from the original query
252 // options, and convert them to the equivalent URL parameters.
254 base::Time end_time =
255 std::min(base::Time::FromInternalValue(options.EffectiveEndTime()),
257 url = net::AppendQueryParameter(url, "max", ServerTimeString(end_time));
259 if (!options.begin_time.is_null()) {
260 url = net::AppendQueryParameter(
261 url, "min", ServerTimeString(options.begin_time));
264 if (options.max_count) {
265 url = net::AppendQueryParameter(
266 url, "num", base::IntToString(options.max_count));
269 if (!text_query.empty())
270 url = net::AppendQueryParameter(url, "q", base::UTF16ToUTF8(text_query));
272 if (!version_info.empty())
273 url = net::AppendQueryParameter(url, "kvi", version_info);
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,
285 base::DictionaryValue* deletion = new base::DictionaryValue;
286 deletion->SetString("type", "CHROME_HISTORY");
288 deletion->SetString("url", url.spec());
289 deletion->SetString("min_timestamp_usec", min_time);
290 deletion->SetString("max_timestamp_usec", max_time);
296 WebHistoryService::Request::Request() {
299 WebHistoryService::Request::~Request() {
302 WebHistoryService::WebHistoryService(Profile* profile)
304 weak_ptr_factory_(this) {
307 WebHistoryService::~WebHistoryService() {
308 STLDeleteElements(&pending_expire_requests_);
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);
319 GURL url = GetQueryUrl(text_query, options, server_version_info_);
320 scoped_ptr<RequestImpl> request(
321 new RequestImpl(profile_, url, completion_callback));
323 return request.PassAs<Request>();
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();
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)
341 std::string max_timestamp = ServerTimeString(end_time);
343 for (std::set<GURL>::const_iterator url_iterator = it->urls.begin();
344 url_iterator != it->urls.end(); ++url_iterator) {
346 CreateDeletion(min_timestamp, max_timestamp, *url_iterator));
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()));
352 delete_request.Set("del", deletions.release());
353 std::string post_data;
354 base::JSONWriter::Write(&delete_request, &post_data);
356 GURL url(kHistoryDeleteHistoryUrl);
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_);
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(),
369 scoped_ptr<RequestImpl> request(
370 new RequestImpl(profile_, url, completion_callback));
371 request->set_post_data(post_data);
373 pending_expire_requests_.insert(request.release());
376 void WebHistoryService::ExpireHistoryBetween(
377 const std::set<GURL>& restrict_urls,
378 base::Time begin_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);
389 void WebHistoryService::QueryHistoryCompletionCallback(
390 const WebHistoryService::QueryWebHistoryCallback& callback,
391 WebHistoryService::Request* request,
393 scoped_ptr<base::DictionaryValue> response_value;
395 response_value = ReadResponse(static_cast<RequestImpl*>(request));
396 callback.Run(request, response_value.get());
399 void WebHistoryService::ExpireHistoryCompletionCallback(
400 const WebHistoryService::ExpireWebHistoryCallback& callback,
401 WebHistoryService::Request* request,
403 scoped_ptr<base::DictionaryValue> response_value;
405 response_value = ReadResponse(static_cast<RequestImpl*>(request));
407 response_value->GetString("version_info", &server_version_info_);
409 callback.Run(response_value.get() && success);
410 // Clean up from pending requests.
411 pending_expire_requests_.erase(request);
415 } // namespace history