blob: b1c8188b88c70b13569db6fa497a296016806a29 [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 "components/doodle/doodle_fetcher_impl.h"
#include <utility>
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/data_use_measurement/core/data_use_user_data.h"
#include "components/google/core/browser/google_url_tracker.h"
#include "components/google/core/browser/google_util.h"
#include "net/base/load_flags.h"
#include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "net/url_request/url_fetcher.h"
using net::URLFetcher;
namespace doodle {
namespace {
// "/async/ddljson" is the base API path. "ntp:1" identifies this request as
// being for a New Tab page. The "graybg:" param specifies whether the doodle
// will be displayed on a gray background.
const char kDoodleConfigPathFormat[] = "/async/ddljson?async=ntp:1,graybg:%d";
std::string StripSafetyPreamble(const std::string& json) {
// The response may start with )]}'. Ignore this.
const char kResponsePreamble[] = ")]}'";
base::StringPiece json_sp(json);
if (json_sp.starts_with(kResponsePreamble)) {
json_sp.remove_prefix(strlen(kResponsePreamble));
}
return json_sp.as_string();
}
GURL BuildDoodleURL(const GURL& base_url,
bool gray_background,
const base::Optional<std::string>& override_url) {
std::string path =
base::StringPrintf(kDoodleConfigPathFormat, gray_background ? 1 : 0);
if (override_url.has_value()) {
// The override URL may or may not be relative to the base URL.
path = *override_url;
}
return base_url.Resolve(path);
}
} // namespace
DoodleFetcherImpl::DoodleFetcherImpl(
scoped_refptr<net::URLRequestContextGetter> download_context,
GoogleURLTracker* google_url_tracker,
const ParseJSONCallback& json_parsing_callback,
bool gray_background,
const base::Optional<std::string>& override_url)
: download_context_(download_context),
google_url_tracker_(google_url_tracker),
json_parsing_callback_(json_parsing_callback),
gray_background_(gray_background),
override_url_(override_url),
weak_ptr_factory_(this) {
DCHECK(google_url_tracker_);
}
DoodleFetcherImpl::~DoodleFetcherImpl() = default;
void DoodleFetcherImpl::FetchDoodle(FinishedCallback callback) {
if (IsFetchInProgress()) {
callbacks_.push_back(std::move(callback));
return; // The callback will be called for the existing request's results.
}
DCHECK(!fetcher_.get());
callbacks_.push_back(std::move(callback));
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("doodle_fetcher", R"(
semantics {
sender: "Doodle Fetcher"
description:
"Retrieves metadata (image URL, clickthrough URL etc) for any "
"currently running Doodle."
trigger:
"Displaying the new tab page on Android."
data: "None."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: false
setting:
"Choosing a non-Google search engine in Chromium settings under "
"'Search Engine' will disable this feature."
policy_exception_justification:
"Not implemented, considered not useful as it does not upload any "
"data."
})");
fetcher_ = URLFetcher::Create(
BuildDoodleURL(GetGoogleBaseUrl(), gray_background_, override_url_),
URLFetcher::GET, this, traffic_annotation);
fetcher_->SetRequestContext(download_context_.get());
fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
net::LOAD_DO_NOT_SAVE_COOKIES |
net::LOAD_DO_NOT_SEND_AUTH_DATA);
fetcher_->SetAutomaticallyRetryOnNetworkChanges(1);
data_use_measurement::DataUseUserData::AttachToFetcher(
fetcher_.get(), data_use_measurement::DataUseUserData::DOODLE);
fetcher_->Start();
}
void DoodleFetcherImpl::OnURLFetchComplete(const URLFetcher* source) {
DCHECK_EQ(fetcher_.get(), source);
std::unique_ptr<net::URLFetcher> free_fetcher = std::move(fetcher_);
std::string json_string;
if (!(source->GetStatus().is_success() &&
source->GetResponseCode() == net::HTTP_OK &&
source->GetResponseAsString(&json_string))) {
RespondToAllCallbacks(DoodleState::DOWNLOAD_ERROR, base::TimeDelta(),
base::nullopt);
return;
}
json_parsing_callback_.Run(StripSafetyPreamble(std::move(json_string)),
base::Bind(&DoodleFetcherImpl::OnJsonParsed,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&DoodleFetcherImpl::OnJsonParseFailed,
weak_ptr_factory_.GetWeakPtr()));
}
void DoodleFetcherImpl::OnJsonParsed(std::unique_ptr<base::Value> json) {
std::unique_ptr<base::DictionaryValue> config =
base::DictionaryValue::From(std::move(json));
if (!config.get()) {
DLOG(WARNING) << "Doodle JSON is not valid.";
RespondToAllCallbacks(DoodleState::PARSING_ERROR, base::TimeDelta(),
base::nullopt);
return;
}
const base::DictionaryValue* ddljson = nullptr;
if (!config->GetDictionary("ddljson", &ddljson)) {
DLOG(WARNING) << "Doodle JSON response did not contain 'ddljson' key.";
RespondToAllCallbacks(DoodleState::PARSING_ERROR, base::TimeDelta(),
base::nullopt);
return;
}
base::TimeDelta time_to_live;
base::Optional<DoodleConfig> doodle =
ParseDoodleConfigAndTimeToLive(*ddljson, &time_to_live);
if (!doodle.has_value()) {
RespondToAllCallbacks(DoodleState::NO_DOODLE, base::TimeDelta(),
base::nullopt);
return;
}
RespondToAllCallbacks(DoodleState::AVAILABLE, time_to_live,
std::move(doodle));
}
void DoodleFetcherImpl::OnJsonParseFailed(const std::string& error_message) {
DLOG(WARNING) << "JSON parsing failed: " << error_message;
RespondToAllCallbacks(DoodleState::PARSING_ERROR, base::TimeDelta(),
base::nullopt);
}
base::Optional<DoodleConfig> DoodleFetcherImpl::ParseDoodleConfigAndTimeToLive(
const base::DictionaryValue& ddljson,
base::TimeDelta* time_to_live) const {
base::Optional<DoodleConfig> doodle =
DoodleConfig::FromDictionary(ddljson, GetGoogleBaseUrl());
// The JSON doesn't guarantee the number to fit into an int.
double ttl_ms = 0; // Expires immediately if the parameter is missing.
if (!ddljson.GetDouble("time_to_live_ms", &ttl_ms) || ttl_ms < 0) {
DLOG(WARNING) << "No valid Doodle TTL present in ddljson!";
ttl_ms = 0;
}
*time_to_live = base::TimeDelta::FromMillisecondsD(ttl_ms);
return doodle;
}
void DoodleFetcherImpl::RespondToAllCallbacks(
DoodleState state,
base::TimeDelta time_to_live,
const base::Optional<DoodleConfig>& config) {
for (auto& callback : callbacks_) {
std::move(callback).Run(state, time_to_live, config);
}
callbacks_.clear();
}
GURL DoodleFetcherImpl::GetGoogleBaseUrl() const {
GURL cmd_line_url = google_util::CommandLineGoogleBaseURL();
if (cmd_line_url.is_valid()) {
return cmd_line_url;
}
return google_url_tracker_->google_url();
}
} // namespace doodle