blob: 6e15628c3b60d7db0adb797a8133d872325267ce [file] [log] [blame]
// Copyright 2017 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 "chrome/browser/search/one_google_bar/one_google_bar_fetcher_impl.h"
#include <string>
#include <utility>
#include "base/callback.h"
#include "base/json/json_writer.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "chrome/browser/search/one_google_bar/one_google_bar_data.h"
#include "chrome/common/chrome_content_client.h"
#include "chrome/common/webui_url_constants.h"
#include "components/google/core/browser/google_url_tracker.h"
#include "components/google/core/browser/google_util.h"
#include "components/signin/core/browser/chrome_connected_header_helper.h"
#include "components/signin/core/browser/signin_header_helper.h"
#include "components/variations/net/variations_http_headers.h"
#include "content/public/common/service_manager_connection.h"
#include "net/base/load_flags.h"
#include "net/base/url_util.h"
#include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_fetcher_delegate.h"
#include "services/data_decoder/public/cpp/safe_json_parser.h"
namespace {
const char kNewTabOgbApiPath[] = "/async/newtab_ogb";
const char kResponsePreamble[] = ")]}'";
// This namespace contains helpers to extract SafeHtml-wrapped strings (see
// https://github.com/google/safe-html-types) from the response json. If there
// is ever a C++ version of the SafeHtml types, we should consider using that
// instead of these custom functions.
namespace safe_html {
bool GetImpl(const base::DictionaryValue& dict,
const std::string& name,
const std::string& wrapped_field_name,
std::string* out) {
const base::DictionaryValue* value = nullptr;
if (!dict.GetDictionary(name, &value)) {
out->clear();
return false;
}
if (!value->GetString(wrapped_field_name, out)) {
out->clear();
return false;
}
return true;
}
bool GetHtml(const base::DictionaryValue& dict,
const std::string& name,
std::string* out) {
return GetImpl(dict, name,
"private_do_not_access_or_else_safe_html_wrapped_value", out);
}
bool GetScript(const base::DictionaryValue& dict,
const std::string& name,
std::string* out) {
return GetImpl(dict, name,
"private_do_not_access_or_else_safe_script_wrapped_value",
out);
}
bool GetStyleSheet(const base::DictionaryValue& dict,
const std::string& name,
std::string* out) {
return GetImpl(dict, name,
"private_do_not_access_or_else_safe_style_sheet_wrapped_value",
out);
}
} // namespace safe_html
base::Optional<OneGoogleBarData> JsonToOGBData(const base::Value& value) {
const base::DictionaryValue* dict = nullptr;
if (!value.GetAsDictionary(&dict)) {
DLOG(WARNING) << "Parse error: top-level dictionary not found";
return base::nullopt;
}
const base::DictionaryValue* update = nullptr;
if (!dict->GetDictionary("update", &update)) {
DLOG(WARNING) << "Parse error: no update";
return base::nullopt;
}
const base::DictionaryValue* one_google_bar = nullptr;
if (!update->GetDictionary("ogb", &one_google_bar)) {
DLOG(WARNING) << "Parse error: no ogb";
return base::nullopt;
}
OneGoogleBarData result;
if (!safe_html::GetHtml(*one_google_bar, "html", &result.bar_html)) {
DLOG(WARNING) << "Parse error: no html";
return base::nullopt;
}
const base::DictionaryValue* page_hooks = nullptr;
if (!one_google_bar->GetDictionary("page_hooks", &page_hooks)) {
DLOG(WARNING) << "Parse error: no page_hooks";
return base::nullopt;
}
safe_html::GetScript(*page_hooks, "in_head_script", &result.in_head_script);
safe_html::GetStyleSheet(*page_hooks, "in_head_style", &result.in_head_style);
safe_html::GetScript(*page_hooks, "after_bar_script",
&result.after_bar_script);
safe_html::GetHtml(*page_hooks, "end_of_body_html", &result.end_of_body_html);
safe_html::GetScript(*page_hooks, "end_of_body_script",
&result.end_of_body_script);
return result;
}
} // namespace
class OneGoogleBarFetcherImpl::AuthenticatedURLFetcher
: public net::URLFetcherDelegate {
public:
using FetchDoneCallback = base::OnceCallback<void(const net::URLFetcher*)>;
AuthenticatedURLFetcher(net::URLRequestContextGetter* request_context,
GURL api_url,
bool account_consistency_mirror_required,
FetchDoneCallback callback);
~AuthenticatedURLFetcher() override = default;
void Start();
private:
std::string GetExtraRequestHeaders() const;
// URLFetcherDelegate implementation.
void OnURLFetchComplete(const net::URLFetcher* source) override;
net::URLRequestContextGetter* const request_context_;
const GURL api_url_;
#if defined(OS_CHROMEOS)
const bool account_consistency_mirror_required_;
#endif
FetchDoneCallback callback_;
// The underlying URLFetcher which does the actual fetch.
std::unique_ptr<net::URLFetcher> url_fetcher_;
};
OneGoogleBarFetcherImpl::AuthenticatedURLFetcher::AuthenticatedURLFetcher(
net::URLRequestContextGetter* request_context,
GURL api_url,
bool account_consistency_mirror_required,
FetchDoneCallback callback)
: request_context_(request_context),
api_url_(std::move(api_url)),
#if defined(OS_CHROMEOS)
account_consistency_mirror_required_(account_consistency_mirror_required),
#endif
callback_(std::move(callback)) {}
std::string
OneGoogleBarFetcherImpl::AuthenticatedURLFetcher::GetExtraRequestHeaders()
const {
net::HttpRequestHeaders headers;
// Note: It's OK to pass SignedIn::kNo if it's unknown, as it does not affect
// transmission of experiments coming from the variations server.
variations::AppendVariationHeaders(api_url_, variations::InIncognito::kNo,
variations::SignedIn::kNo, &headers);
#if defined(OS_CHROMEOS)
signin::ChromeConnectedHeaderHelper chrome_connected_header_helper(
account_consistency_mirror_required_
? signin::AccountConsistencyMethod::kMirror
: signin::AccountConsistencyMethod::kDisabled);
int profile_mode = signin::PROFILE_MODE_DEFAULT;
if (account_consistency_mirror_required_) {
// For the child account case (where currently
// |account_consistency_mirror_required_| is true on Chrome OS), we always
// want to disable adding an account and going to incognito.
profile_mode = signin::PROFILE_MODE_INCOGNITO_DISABLED |
signin::PROFILE_MODE_ADD_ACCOUNT_DISABLED;
}
std::string chrome_connected_header_value =
chrome_connected_header_helper.BuildRequestHeader(
/*is_header_request=*/true, api_url_,
// Account ID is only needed for (drive|docs).google.com.
/*account_id=*/std::string(), profile_mode);
if (!chrome_connected_header_value.empty()) {
headers.SetHeader(signin::kChromeConnectedHeader,
chrome_connected_header_value);
}
#endif
return headers.ToString();
}
void OneGoogleBarFetcherImpl::AuthenticatedURLFetcher::Start() {
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("one_google_bar_service", R"(
semantics {
sender: "One Google Bar Service"
description: "Downloads the 'One Google' bar."
trigger:
"Displaying the new tab page on Desktop, if Google is the "
"configured search provider."
data: "Credentials if user is signed in."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting:
"Users can control this feature via selecting a non-Google default "
"search engine in Chrome settings under 'Search Engine'."
chrome_policy {
DefaultSearchProviderEnabled {
policy_options {mode: MANDATORY}
DefaultSearchProviderEnabled: false
}
}
})");
url_fetcher_ = net::URLFetcher::Create(0, api_url_, net::URLFetcher::GET,
this, traffic_annotation);
url_fetcher_->SetRequestContext(request_context_);
url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_AUTH_DATA);
url_fetcher_->SetExtraRequestHeaders(GetExtraRequestHeaders());
url_fetcher_->SetInitiator(
url::Origin::Create(GURL(chrome::kChromeUINewTabURL)));
url_fetcher_->Start();
}
void OneGoogleBarFetcherImpl::AuthenticatedURLFetcher::OnURLFetchComplete(
const net::URLFetcher* source) {
std::move(callback_).Run(source);
}
OneGoogleBarFetcherImpl::OneGoogleBarFetcherImpl(
net::URLRequestContextGetter* request_context,
GoogleURLTracker* google_url_tracker,
const std::string& application_locale,
const base::Optional<std::string>& api_url_override,
bool account_consistency_mirror_required)
: request_context_(request_context),
google_url_tracker_(google_url_tracker),
application_locale_(application_locale),
api_url_override_(api_url_override),
account_consistency_mirror_required_(account_consistency_mirror_required),
weak_ptr_factory_(this) {}
OneGoogleBarFetcherImpl::~OneGoogleBarFetcherImpl() = default;
void OneGoogleBarFetcherImpl::Fetch(OneGoogleCallback callback) {
callbacks_.push_back(std::move(callback));
// Note: If there is an ongoing request, abandon it. It's possible that
// something has changed in the meantime (e.g. signin state) that would make
// the result obsolete.
pending_request_ = std::make_unique<AuthenticatedURLFetcher>(
request_context_, GetApiUrl(), account_consistency_mirror_required_,
base::BindOnce(&OneGoogleBarFetcherImpl::FetchDone,
base::Unretained(this)));
pending_request_->Start();
}
GURL OneGoogleBarFetcherImpl::GetFetchURLForTesting() const {
return GetApiUrl();
}
GURL OneGoogleBarFetcherImpl::GetApiUrl() const {
GURL google_base_url = google_util::CommandLineGoogleBaseURL();
if (!google_base_url.is_valid()) {
google_base_url = google_url_tracker_->google_url();
}
GURL api_url =
google_base_url.Resolve(api_url_override_.value_or(kNewTabOgbApiPath));
// Add the "hl=" parameter.
api_url = net::AppendQueryParameter(api_url, "hl", application_locale_);
// Add the "async=" parameter. We can't use net::AppendQueryParameter for
// this because we need the ":" to remain unescaped.
GURL::Replacements replacements;
std::string query = api_url.query();
query += "&async=fixed:0";
replacements.SetQueryStr(query);
return api_url.ReplaceComponents(replacements);
}
void OneGoogleBarFetcherImpl::FetchDone(const net::URLFetcher* source) {
// The fetcher will be deleted when the request is handled.
std::unique_ptr<AuthenticatedURLFetcher> deleter(std::move(pending_request_));
const net::URLRequestStatus& request_status = source->GetStatus();
if (request_status.status() != net::URLRequestStatus::SUCCESS) {
// This represents network errors (i.e. the server did not provide a
// response).
DLOG(WARNING) << "Request failed with error: " << request_status.error()
<< ": " << net::ErrorToString(request_status.error());
Respond(Status::TRANSIENT_ERROR, base::nullopt);
return;
}
const int response_code = source->GetResponseCode();
if (response_code != net::HTTP_OK) {
DLOG(WARNING) << "Response code: " << response_code;
std::string response;
source->GetResponseAsString(&response);
DLOG(WARNING) << "Response: " << response;
Respond(Status::FATAL_ERROR, base::nullopt);
return;
}
std::string response;
bool success = source->GetResponseAsString(&response);
DCHECK(success);
// The response may start with )]}'. Ignore this.
if (base::StartsWith(response, kResponsePreamble,
base::CompareCase::SENSITIVE)) {
response = response.substr(strlen(kResponsePreamble));
}
data_decoder::SafeJsonParser::Parse(
content::ServiceManagerConnection::GetForProcess()->GetConnector(),
response,
base::Bind(&OneGoogleBarFetcherImpl::JsonParsed,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&OneGoogleBarFetcherImpl::JsonParseFailed,
weak_ptr_factory_.GetWeakPtr()));
}
void OneGoogleBarFetcherImpl::JsonParsed(std::unique_ptr<base::Value> value) {
base::Optional<OneGoogleBarData> result = JsonToOGBData(*value);
Respond(result.has_value() ? Status::OK : Status::FATAL_ERROR, result);
}
void OneGoogleBarFetcherImpl::JsonParseFailed(const std::string& message) {
DLOG(WARNING) << "Parsing JSON failed: " << message;
Respond(Status::FATAL_ERROR, base::nullopt);
}
void OneGoogleBarFetcherImpl::Respond(
Status status,
const base::Optional<OneGoogleBarData>& data) {
for (auto& callback : callbacks_) {
std::move(callback).Run(status, data);
}
callbacks_.clear();
}