blob: e1f687a4d09181ce81f4f6cb6c0a2906d6a86310 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/history/core/browser/web_history_service.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/optional.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "components/history/core/browser/history_service_observer.h"
#include "components/history/core/browser/web_history_service_observer.h"
#include "components/signin/public/identity_manager/access_token_info.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/primary_account_access_token_fetcher.h"
#include "components/sync/driver/sync_util.h"
#include "components/sync/protocol/history_status.pb.h"
#include "google_apis/gaia/gaia_urls.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "net/base/load_flags.h"
#include "net/base/url_util.h"
#include "net/http/http_status_code.h"
#include "net/http/http_util.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "url/gurl.h"
namespace history {
namespace {
const char kHistoryOAuthScope[] =
"https://www.googleapis.com/auth/chromesync";
const char kHistoryQueryHistoryUrl[] =
"https://history.google.com/history/api/lookup?client=chrome";
const char kHistoryDeleteHistoryUrl[] =
"https://history.google.com/history/api/delete?client=chrome";
const char kHistoryAudioHistoryUrl[] =
"https://history.google.com/history/api/lookup?client=audio";
const char kHistoryAudioHistoryChangeUrl[] =
"https://history.google.com/history/api/change";
const char kQueryWebAndAppActivityUrl[] =
"https://history.google.com/history/api/lookup?client=web_app";
const char kQueryOtherFormsOfBrowsingHistoryUrlSuffix[] = "/historystatus";
const char kPostDataMimeType[] = "text/plain";
const char kSyncProtoMimeType[] = "application/octet-stream";
// The maximum number of retries for the SimpleURLLoader requests.
const size_t kMaxRetries = 1;
class RequestImpl : public WebHistoryService::Request {
public:
~RequestImpl() override {}
// Returns the response code received from the server, which will only be
// valid if the request succeeded.
int GetResponseCode() override { return response_code_; }
// Returns the contents of the response body received from the server.
const std::string& GetResponseBody() override { return response_body_; }
bool IsPending() override { return is_pending_; }
private:
friend class history::WebHistoryService;
RequestImpl(
signin::IdentityManager* identity_manager,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
const GURL& url,
WebHistoryService::CompletionCallback callback,
const net::PartialNetworkTrafficAnnotationTag& partial_traffic_annotation)
: identity_manager_(identity_manager),
url_loader_factory_(std::move(url_loader_factory)),
url_(url),
post_data_mime_type_(kPostDataMimeType),
response_code_(0),
auth_retry_count_(0),
callback_(std::move(callback)),
is_pending_(false),
partial_traffic_annotation_(partial_traffic_annotation) {
DCHECK(identity_manager_);
DCHECK(url_loader_factory_);
}
void OnAccessTokenFetchComplete(GoogleServiceAuthError error,
signin::AccessTokenInfo access_token_info) {
access_token_fetcher_.reset();
if (error.state() != GoogleServiceAuthError::NONE) {
is_pending_ = false;
UMA_HISTOGRAM_BOOLEAN("WebHistory.OAuthTokenCompletion", false);
std::move(callback_).Run(this, false);
// It is valid for the callback to delete |this|, so do not access any
// members below here.
return;
}
DCHECK(!access_token_info.token.empty());
access_token_ = access_token_info.token;
UMA_HISTOGRAM_BOOLEAN("WebHistory.OAuthTokenCompletion", true);
// Got an access token -- start the actual API request.
net::NetworkTrafficAnnotationTag traffic_annotation =
net::CompleteNetworkTrafficAnnotation("web_history_service",
partial_traffic_annotation_,
R"(
semantics {
sender: "Web History"
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting:
"To disable this feature, users can either sign out or disable "
"history sync via unchecking 'History' setting under 'Advanced "
"sync settings."
})");
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = url_;
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
resource_request->method = post_data_ ? "POST" : "GET";
resource_request->headers.SetHeader(net::HttpRequestHeaders::kAuthorization,
"Bearer " + access_token_info.token);
resource_request->headers.SetHeader(
"X-Developer-Key", GaiaUrls::GetInstance()->oauth2_chrome_client_id());
if (!user_agent_.empty()) {
resource_request->headers.SetHeader(net::HttpRequestHeaders::kUserAgent,
user_agent_);
}
simple_url_loader_ = network::SimpleURLLoader::Create(
std::move(resource_request), traffic_annotation);
if (post_data_) {
simple_url_loader_->AttachStringForUpload(post_data_.value(),
post_data_mime_type_);
}
simple_url_loader_->SetRetryOptions(kMaxRetries,
network::SimpleURLLoader::RETRY_ON_5XX);
simple_url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
url_loader_factory_.get(),
base::BindOnce(&RequestImpl::OnSimpleLoaderComplete,
base::Unretained(this)));
}
// Tells the request to do its thang.
void Start() override {
identity::ScopeSet oauth_scopes;
oauth_scopes.insert(kHistoryOAuthScope);
access_token_fetcher_ =
std::make_unique<signin::PrimaryAccountAccessTokenFetcher>(
"web_history", identity_manager_, oauth_scopes,
base::BindOnce(&RequestImpl::OnAccessTokenFetchComplete,
base::Unretained(this)),
signin::PrimaryAccountAccessTokenFetcher::Mode::kImmediate);
is_pending_ = true;
}
void OnSimpleLoaderComplete(std::unique_ptr<std::string> response_body) {
response_code_ = -1;
if (simple_url_loader_->ResponseInfo() &&
simple_url_loader_->ResponseInfo()->headers) {
response_code_ =
simple_url_loader_->ResponseInfo()->headers->response_code();
}
simple_url_loader_.reset();
UMA_HISTOGRAM_CUSTOM_ENUMERATION("WebHistory.OAuthTokenResponseCode",
net::HttpUtil::MapStatusCodeForHistogram(response_code_),
net::HttpUtil::GetStatusCodesForHistogram());
// If the response code indicates that the token might not be valid,
// invalidate the token and try again.
if (response_code_ == net::HTTP_UNAUTHORIZED && ++auth_retry_count_ <= 1) {
identity::ScopeSet oauth_scopes;
oauth_scopes.insert(kHistoryOAuthScope);
identity_manager_->RemoveAccessTokenFromCache(
identity_manager_->GetPrimaryAccountId(), oauth_scopes,
access_token_);
access_token_.clear();
Start();
return;
}
if (response_body) {
response_body_ = std::move(*response_body);
} else {
response_body_.clear();
}
is_pending_ = false;
std::move(callback_).Run(this, true);
// It is valid for the callback to delete |this|, so do not access any
// members below here.
}
void SetPostData(const std::string& post_data) override {
SetPostDataAndType(post_data, kPostDataMimeType);
}
void SetPostDataAndType(const std::string& post_data,
const std::string& mime_type) override {
post_data_ = post_data;
post_data_mime_type_ = mime_type;
}
void SetUserAgent(const std::string& user_agent) override {
user_agent_ = user_agent;
}
signin::IdentityManager* identity_manager_;
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
// The URL of the API endpoint.
GURL url_;
// POST data to be sent with the request (may be empty).
base::Optional<std::string> post_data_;
// MIME type of the post requests. Defaults to text/plain.
std::string post_data_mime_type_;
// The user agent header used with this request.
std::string user_agent_;
std::unique_ptr<signin::PrimaryAccountAccessTokenFetcher>
access_token_fetcher_;
// The current OAuth2 access token.
std::string access_token_;
// Handles the actual API requests after the OAuth token is acquired.
std::unique_ptr<network::SimpleURLLoader> simple_url_loader_;
// Holds the response code received from the server.
int response_code_;
// Holds the response body received from the server.
std::string response_body_;
// The number of times this request has already been retried due to
// authorization problems.
int auth_retry_count_;
// The callback to execute when the query is complete.
WebHistoryService::CompletionCallback callback_;
// True if the request was started and has not yet completed, otherwise false.
bool is_pending_;
// Partial Network traffic annotation used to create SimpleURLLoader for this
// request.
const net::PartialNetworkTrafficAnnotationTag partial_traffic_annotation_;
};
// Converts a time into a string for use as a parameter in a request to the
// history server.
std::string ServerTimeString(base::Time time) {
if (time < base::Time::UnixEpoch())
return base::NumberToString(0);
return base::NumberToString(
(time - base::Time::UnixEpoch()).InMicroseconds());
}
// Returns a URL for querying the history server for a query specified by
// |options|. |version_info|, if not empty, should be a token that was received
// from the server in response to a write operation. It is used to help ensure
// read consistency after a write.
GURL GetQueryUrl(const base::string16& text_query,
const QueryOptions& options,
const std::string& version_info) {
GURL url = GURL(kHistoryQueryHistoryUrl);
url = net::AppendQueryParameter(url, "titles", "1");
// Take |begin_time|, |end_time|, and |max_count| from the original query
// options, and convert them to the equivalent URL parameters. Note that
// QueryOptions uses exclusive |end_time| while the history.google.com API
// uses it inclusively, so we subtract 1us during conversion.
base::Time end_time =
options.end_time.is_null()
? base::Time::Now()
: std::min(options.end_time - base::TimeDelta::FromMicroseconds(1),
base::Time::Now());
url = net::AppendQueryParameter(url, "max", ServerTimeString(end_time));
if (!options.begin_time.is_null()) {
url = net::AppendQueryParameter(
url, "min", ServerTimeString(options.begin_time));
}
if (options.max_count) {
url = net::AppendQueryParameter(url, "num",
base::NumberToString(options.max_count));
}
if (!text_query.empty())
url = net::AppendQueryParameter(url, "q", base::UTF16ToUTF8(text_query));
if (!version_info.empty())
url = net::AppendQueryParameter(url, "kvi", version_info);
return url;
}
// Creates a DictionaryValue to hold the parameters for a deletion.
// Ownership is passed to the caller.
// |url| may be empty, indicating a time-range deletion.
std::unique_ptr<base::DictionaryValue> CreateDeletion(
const std::string& min_time,
const std::string& max_time,
const GURL& url) {
std::unique_ptr<base::DictionaryValue> deletion(new base::DictionaryValue);
deletion->SetString("type", "CHROME_HISTORY");
if (url.is_valid())
deletion->SetString("url", url.spec());
deletion->SetString("min_timestamp_usec", min_time);
deletion->SetString("max_timestamp_usec", max_time);
return deletion;
}
} // namespace
WebHistoryService::Request::Request() {
}
WebHistoryService::Request::~Request() {
}
WebHistoryService::WebHistoryService(
signin::IdentityManager* identity_manager,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: identity_manager_(identity_manager),
url_loader_factory_(std::move(url_loader_factory)) {}
WebHistoryService::~WebHistoryService() {
}
void WebHistoryService::AddObserver(WebHistoryServiceObserver* observer) {
observer_list_.AddObserver(observer);
}
void WebHistoryService::RemoveObserver(WebHistoryServiceObserver* observer) {
observer_list_.RemoveObserver(observer);
}
WebHistoryService::Request* WebHistoryService::CreateRequest(
const GURL& url,
CompletionCallback callback,
const net::PartialNetworkTrafficAnnotationTag& partial_traffic_annotation) {
return new RequestImpl(identity_manager_, url_loader_factory_, url,
std::move(callback), partial_traffic_annotation);
}
// static
std::unique_ptr<base::DictionaryValue> WebHistoryService::ReadResponse(
WebHistoryService::Request* request) {
std::unique_ptr<base::DictionaryValue> result;
if (request->GetResponseCode() == net::HTTP_OK) {
std::unique_ptr<base::Value> value =
base::JSONReader::ReadDeprecated(request->GetResponseBody());
if (value && value->is_dict())
result.reset(static_cast<base::DictionaryValue*>(value.release()));
else
DLOG(WARNING) << "Non-JSON response received from history server.";
}
return result;
}
std::unique_ptr<WebHistoryService::Request> WebHistoryService::QueryHistory(
const base::string16& text_query,
const QueryOptions& options,
WebHistoryService::QueryWebHistoryCallback callback,
const net::PartialNetworkTrafficAnnotationTag& partial_traffic_annotation) {
// Wrap the original callback into a generic completion callback.
CompletionCallback completion_callback = base::BindOnce(
&WebHistoryService::QueryHistoryCompletionCallback, std::move(callback));
GURL url = GetQueryUrl(text_query, options, server_version_info_);
std::unique_ptr<Request> request(CreateRequest(
url, std::move(completion_callback), partial_traffic_annotation));
request->Start();
return request;
}
void WebHistoryService::ExpireHistory(
const std::vector<ExpireHistoryArgs>& expire_list,
ExpireWebHistoryCallback callback,
const net::PartialNetworkTrafficAnnotationTag& partial_traffic_annotation) {
base::DictionaryValue delete_request;
std::unique_ptr<base::ListValue> deletions(new base::ListValue);
base::Time now = base::Time::Now();
for (const auto& expire : expire_list) {
// Convert the times to server timestamps.
std::string min_timestamp = ServerTimeString(expire.begin_time);
// TODO(dubroy): Use sane time (crbug.com/146090) here when it's available.
base::Time end_time = expire.end_time;
if (end_time.is_null() || end_time > now)
end_time = now;
std::string max_timestamp = ServerTimeString(end_time);
for (const auto& url : expire.urls) {
deletions->Append(
CreateDeletion(min_timestamp, max_timestamp, url));
}
// If no URLs were specified, delete everything in the time range.
if (expire.urls.empty())
deletions->Append(CreateDeletion(min_timestamp, max_timestamp, GURL()));
}
delete_request.Set("del", std::move(deletions));
std::string post_data;
base::JSONWriter::Write(delete_request, &post_data);
GURL url(kHistoryDeleteHistoryUrl);
// Append the version info token, if it is available, to help ensure
// consistency with any previous deletions.
if (!server_version_info_.empty())
url = net::AppendQueryParameter(url, "kvi", server_version_info_);
// Wrap the original callback into a generic completion callback.
CompletionCallback completion_callback =
base::BindOnce(&WebHistoryService::ExpireHistoryCompletionCallback,
weak_ptr_factory_.GetWeakPtr(), std::move(callback));
std::unique_ptr<Request> request(CreateRequest(
url, std::move(completion_callback), partial_traffic_annotation));
request->SetPostData(post_data);
Request* request_ptr = request.get();
pending_expire_requests_[request_ptr] = std::move(request);
request_ptr->Start();
}
void WebHistoryService::ExpireHistoryBetween(
const std::set<GURL>& restrict_urls,
base::Time begin_time,
base::Time end_time,
ExpireWebHistoryCallback callback,
const net::PartialNetworkTrafficAnnotationTag& partial_traffic_annotation) {
std::vector<ExpireHistoryArgs> expire_list(1);
expire_list.back().urls = restrict_urls;
expire_list.back().begin_time = begin_time;
expire_list.back().end_time = end_time;
ExpireHistory(expire_list, std::move(callback), partial_traffic_annotation);
}
void WebHistoryService::GetAudioHistoryEnabled(
AudioWebHistoryCallback callback,
const net::PartialNetworkTrafficAnnotationTag& partial_traffic_annotation) {
// Wrap the original callback into a generic completion callback.
CompletionCallback completion_callback =
base::BindOnce(&WebHistoryService::AudioHistoryCompletionCallback,
weak_ptr_factory_.GetWeakPtr(), std::move(callback));
GURL url(kHistoryAudioHistoryUrl);
std::unique_ptr<Request> request(CreateRequest(
url, std::move(completion_callback), partial_traffic_annotation));
request->Start();
Request* request_ptr = request.get();
pending_audio_history_requests_[request_ptr] = std::move(request);
}
void WebHistoryService::SetAudioHistoryEnabled(
bool new_enabled_value,
AudioWebHistoryCallback callback,
const net::PartialNetworkTrafficAnnotationTag& partial_traffic_annotation) {
// Wrap the original callback into a generic completion callback.
CompletionCallback completion_callback =
base::BindOnce(&WebHistoryService::AudioHistoryCompletionCallback,
weak_ptr_factory_.GetWeakPtr(), std::move(callback));
GURL url(kHistoryAudioHistoryChangeUrl);
std::unique_ptr<Request> request(CreateRequest(
url, std::move(completion_callback), partial_traffic_annotation));
base::DictionaryValue enable_audio_history;
enable_audio_history.SetBoolean("enable_history_recording",
new_enabled_value);
enable_audio_history.SetString("client", "audio");
std::string post_data;
base::JSONWriter::Write(enable_audio_history, &post_data);
request->SetPostData(post_data);
request->Start();
Request* request_ptr = request.get();
pending_audio_history_requests_[request_ptr] = std::move(request);
}
size_t WebHistoryService::GetNumberOfPendingAudioHistoryRequests() {
return pending_audio_history_requests_.size();
}
void WebHistoryService::QueryWebAndAppActivity(
QueryWebAndAppActivityCallback callback,
const net::PartialNetworkTrafficAnnotationTag& partial_traffic_annotation) {
// Wrap the original callback into a generic completion callback.
CompletionCallback completion_callback = base::BindOnce(
&WebHistoryService::QueryWebAndAppActivityCompletionCallback,
weak_ptr_factory_.GetWeakPtr(), std::move(callback));
GURL url(kQueryWebAndAppActivityUrl);
Request* request = CreateRequest(url, std::move(completion_callback),
partial_traffic_annotation);
pending_web_and_app_activity_requests_[request] = base::WrapUnique(request);
request->Start();
}
void WebHistoryService::QueryOtherFormsOfBrowsingHistory(
version_info::Channel channel,
QueryOtherFormsOfBrowsingHistoryCallback callback,
const net::PartialNetworkTrafficAnnotationTag& partial_traffic_annotation) {
// Wrap the original callback into a generic completion callback.
CompletionCallback completion_callback = base::BindOnce(
&WebHistoryService::QueryOtherFormsOfBrowsingHistoryCompletionCallback,
weak_ptr_factory_.GetWeakPtr(), std::move(callback));
// Find the Sync request URL.
GURL url = syncer::GetSyncServiceURL(*base::CommandLine::ForCurrentProcess(),
channel);
GURL::Replacements replace_path;
std::string new_path =
url.path() + kQueryOtherFormsOfBrowsingHistoryUrlSuffix;
replace_path.SetPathStr(new_path);
url = url.ReplaceComponents(replace_path);
DCHECK(url.is_valid());
Request* request = CreateRequest(url, std::move(completion_callback),
partial_traffic_annotation);
// Set the Sync-specific user agent.
request->SetUserAgent(syncer::MakeUserAgentForSync(channel));
pending_other_forms_of_browsing_history_requests_[request] =
base::WrapUnique(request);
// Set the request protobuf.
sync_pb::HistoryStatusRequest request_proto;
std::string post_data;
request_proto.SerializeToString(&post_data);
request->SetPostDataAndType(post_data, kSyncProtoMimeType);
request->Start();
}
// static
void WebHistoryService::QueryHistoryCompletionCallback(
WebHistoryService::QueryWebHistoryCallback callback,
WebHistoryService::Request* request,
bool success) {
std::unique_ptr<base::DictionaryValue> response_value;
if (success)
response_value = ReadResponse(request);
std::move(callback).Run(request, response_value.get());
}
void WebHistoryService::ExpireHistoryCompletionCallback(
WebHistoryService::ExpireWebHistoryCallback callback,
WebHistoryService::Request* request,
bool success) {
std::unique_ptr<Request> request_ptr =
std::move(pending_expire_requests_[request]);
pending_expire_requests_.erase(request);
std::unique_ptr<base::DictionaryValue> response_value;
if (success) {
response_value = ReadResponse(request);
if (response_value)
response_value->GetString("version_info", &server_version_info_);
}
// Inform the observers about the history deletion.
if (response_value.get() && success) {
for (WebHistoryServiceObserver& observer : observer_list_)
observer.OnWebHistoryDeleted();
}
std::move(callback).Run(response_value.get() && success);
}
void WebHistoryService::AudioHistoryCompletionCallback(
WebHistoryService::AudioWebHistoryCallback callback,
WebHistoryService::Request* request,
bool success) {
std::unique_ptr<Request> request_ptr =
std::move(pending_audio_history_requests_[request]);
pending_audio_history_requests_.erase(request);
std::unique_ptr<base::DictionaryValue> response_value;
bool enabled_value = false;
if (success) {
response_value = ReadResponse(request);
if (response_value)
response_value->GetBoolean("history_recording_enabled", &enabled_value);
}
// If there is no response_value, then for our purposes, the request has
// failed, despite receiving a true |success| value. This can happen if
// the user is offline.
std::move(callback).Run(success && response_value, enabled_value);
}
void WebHistoryService::QueryWebAndAppActivityCompletionCallback(
WebHistoryService::QueryWebAndAppActivityCallback callback,
WebHistoryService::Request* request,
bool success) {
std::unique_ptr<Request> request_ptr =
std::move(pending_web_and_app_activity_requests_[request]);
pending_web_and_app_activity_requests_.erase(request);
std::unique_ptr<base::DictionaryValue> response_value;
bool web_and_app_activity_enabled = false;
if (success) {
response_value = ReadResponse(request);
if (response_value) {
response_value->GetBoolean(
"history_recording_enabled", &web_and_app_activity_enabled);
}
}
std::move(callback).Run(web_and_app_activity_enabled);
}
void WebHistoryService::QueryOtherFormsOfBrowsingHistoryCompletionCallback(
WebHistoryService::QueryOtherFormsOfBrowsingHistoryCallback callback,
WebHistoryService::Request* request,
bool success) {
std::unique_ptr<Request> request_ptr =
std::move(pending_other_forms_of_browsing_history_requests_[request]);
pending_other_forms_of_browsing_history_requests_.erase(request);
bool has_other_forms_of_browsing_history = false;
if (success && request->GetResponseCode() == net::HTTP_OK) {
sync_pb::HistoryStatusResponse history_status;
if (history_status.ParseFromString(request->GetResponseBody()))
has_other_forms_of_browsing_history = history_status.has_derived_data();
}
std::move(callback).Run(has_other_forms_of_browsing_history);
}
} // namespace history