- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / history / web_history_service.cc
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 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 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