blob: b8262465be87bffa502411a94f217dcf0110aa41 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/client_hints/browser/client_hints.h"
#include <cmath>
#include <functional>
#include <string>
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/json/json_reader.h"
#include "components/client_hints/common/client_hints.h"
#include "components/client_hints/common/switches.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings_constraints.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/content_settings/core/common/content_settings_utils.h"
#include "components/embedder_support/user_agent_utils.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "services/network/public/cpp/client_hints.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "third_party/blink/public/common/client_hints/enabled_client_hints.h"
#include "third_party/blink/public/common/features.h"
namespace client_hints {
namespace {
base::flat_map<url::Origin, std::vector<network::mojom::WebClientHintsType>>
ParseInitializeClientHintsStorage() {
auto results =
base::flat_map<url::Origin,
std::vector<network::mojom::WebClientHintsType>>();
std::string raw_client_hint_json =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kInitializeClientHintsStorage);
std::optional<base::Value::Dict> maybe_value =
base::JSONReader::ReadDict(raw_client_hint_json);
if (!maybe_value) {
LOG(WARNING)
<< "The 'initialize-client-hints-storage' switch value could not be "
<< "properly parsed.";
return {};
}
for (auto entry : *maybe_value) {
url::Origin origin = url::Origin::Create(GURL(entry.first));
if (origin.opaque() || origin.scheme() != url::kHttpsScheme) {
LOG(WARNING)
<< "The url '" << entry.first
<< "' cannot be associated to client hints and will be ignored.";
continue;
}
if (!entry.second.is_string()) {
LOG(WARNING) << "The value associated with the origin \""
<< origin.Serialize() << "\" could not be recognized as a "
<< "valid string and will be ignored.";
continue;
}
std::optional<std::vector<network::mojom::WebClientHintsType>>
maybe_parsed_accept_ch =
network::ParseClientHintsHeader(entry.second.GetString());
if (!maybe_parsed_accept_ch) {
LOG(WARNING) << "Could not parse the following client hint token list: "
<< entry.second.GetString();
continue;
}
results[origin] = maybe_parsed_accept_ch.value();
}
return results;
}
} // namespace
ClientHints::ClientHints(
content::BrowserContext* context,
network::NetworkQualityTracker* network_quality_tracker,
HostContentSettingsMap* settings_map,
scoped_refptr<content_settings::CookieSettings> cookie_settings,
PrefService* pref_service)
: context_(context),
network_quality_tracker_(network_quality_tracker),
settings_map_(settings_map),
cookie_settings_(cookie_settings),
pref_service_(pref_service) {
DCHECK(context_);
DCHECK(network_quality_tracker_);
DCHECK(settings_map_);
DCHECK(cookie_settings_);
if (!context->IsOffTheRecord() &&
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kInitializeClientHintsStorage)) {
auto command_line_hints = ParseInitializeClientHintsStorage();
for (const auto& origin_hints_pair : command_line_hints) {
PersistClientHints(origin_hints_pair.first, nullptr,
origin_hints_pair.second);
}
}
}
ClientHints::~ClientHints() = default;
network::NetworkQualityTracker* ClientHints::GetNetworkQualityTracker() {
return network_quality_tracker_;
}
void ClientHints::GetAllowedClientHintsFromSource(
const url::Origin& origin,
blink::EnabledClientHints* client_hints) {
if (network::features::ShouldBlockAcceptClientHintsFor(origin)) {
return;
}
const GURL& url = origin.GetURL();
if (!network::IsUrlPotentiallyTrustworthy(url)) {
return;
}
client_hints::GetAllowedClientHints(
settings_map_->GetWebsiteSetting(
url, GURL(), ContentSettingsType::CLIENT_HINTS, nullptr),
client_hints);
for (auto hint : additional_hints_) {
client_hints->SetIsEnabled(hint, true);
}
}
bool ClientHints::IsJavaScriptAllowed(const GURL& url,
content::RenderFrameHost* parent_rfh) {
return settings_map_->GetContentSetting(
parent_rfh ? parent_rfh->GetOutermostMainFrame()
->GetLastCommittedOrigin()
.GetURL()
: url,
url, ContentSettingsType::JAVASCRIPT) != CONTENT_SETTING_BLOCK;
}
blink::UserAgentMetadata ClientHints::GetUserAgentMetadata() {
return embedder_support::GetUserAgentMetadata(pref_service_);
}
void ClientHints::PersistClientHints(
const url::Origin& primary_origin,
content::RenderFrameHost* parent_rfh,
const std::vector<network::mojom::WebClientHintsType>& client_hints) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const GURL primary_url = primary_origin.GetURL();
// TODO(tbansal): crbug.com/735518. Consider killing the renderer that sent
// the malformed IPC.
if (!primary_url.is_valid() ||
!network::IsUrlPotentiallyTrustworthy(primary_url)) {
return;
}
if (!IsJavaScriptAllowed(primary_url, parent_rfh)) {
return;
}
DCHECK_LE(
client_hints.size(),
static_cast<size_t>(network::mojom::WebClientHintsType::kMaxValue) + 1);
if (client_hints.size() >
(static_cast<size_t>(network::mojom::WebClientHintsType::kMaxValue) +
1)) {
// Return early if the list does not have the right number of values.
// Persisting wrong number of values to the disk may cause errors when
// reading them back in the future.
return;
}
const auto& persistence_started = base::TimeTicks::Now();
base::Value::List client_hints_list;
client_hints_list.reserve(client_hints.size());
for (const auto& entry : client_hints) {
client_hints_list.Append(static_cast<int>(entry));
}
base::Value::Dict client_hints_dictionary;
client_hints_dictionary.Set(kClientHintsSettingKey,
std::move(client_hints_list));
// TODO(tbansal): crbug.com/735518. Disable updates to client hints settings
// when cookies are disabled for |primary_origin|.
content_settings::ContentSettingConstraints constraints;
constraints.set_session_model(content_settings::mojom::SessionModel::DURABLE);
settings_map_->SetWebsiteSettingDefaultScope(
primary_url, GURL(), ContentSettingsType::CLIENT_HINTS,
base::Value(std::move(client_hints_dictionary)), constraints);
network::LogClientHintsPersistenceMetrics(persistence_started,
client_hints.size());
}
void ClientHints::SetAdditionalClientHints(
const std::vector<network::mojom::WebClientHintsType>& hints) {
additional_hints_ = hints;
}
void ClientHints::ClearAdditionalClientHints() {
additional_hints_.clear();
}
void ClientHints::SetMostRecentMainFrameViewportSize(
const gfx::Size& viewport_size) {
viewport_size_ = viewport_size;
}
gfx::Size ClientHints::GetMostRecentMainFrameViewportSize() {
return viewport_size_;
}
void ClientHints::ForceEmptyViewportSizeForTesting(
bool should_force_empty_viewport_size) {
should_force_empty_viewport_size_ = should_force_empty_viewport_size;
}
bool ClientHints::ShouldForceEmptyViewportSize() {
return should_force_empty_viewport_size_;
}
} // namespace client_hints