blob: 416dd579f0b71e58a0dc540a7db9458861c63beb [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 "services/network/public/cpp/client_hints.h"
#include <algorithm>
#include <optional>
#include <utility>
#include <vector>
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_tokenizer.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "net/http/structured_headers.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace network {
const char kPrefersColorSchemeDark[] = "dark";
const char kPrefersColorSchemeLight[] = "light";
const char kPrefersReducedMotionNoPreference[] = "no-preference";
const char kPrefersReducedMotionReduce[] = "reduce";
const char kPrefersReducedTransparencyNoPreference[] = "no-preference";
const char kPrefersReducedTransparencyReduce[] = "reduce";
const char* const kWebEffectiveConnectionTypeMapping[] = {
"4g" /* Unknown */, "4g" /* Offline */, "slow-2g" /* Slow 2G */,
"2g" /* 2G */, "3g" /* 3G */, "4g" /* 4G */
};
const size_t kWebEffectiveConnectionTypeMappingCount =
std::size(kWebEffectiveConnectionTypeMapping);
ClientHintToNameMap MakeClientHintToNameMap() {
return {
{network::mojom::WebClientHintsType::kDeviceMemory_DEPRECATED,
"device-memory"},
{network::mojom::WebClientHintsType::kDpr_DEPRECATED, "dpr"},
{network::mojom::WebClientHintsType::kResourceWidth_DEPRECATED, "width"},
{network::mojom::WebClientHintsType::kViewportWidth_DEPRECATED,
"viewport-width"},
{network::mojom::WebClientHintsType::kRtt_DEPRECATED, "rtt"},
{network::mojom::WebClientHintsType::kDownlink_DEPRECATED, "downlink"},
{network::mojom::WebClientHintsType::kEct_DEPRECATED, "ect"},
{network::mojom::WebClientHintsType::kUA, "sec-ch-ua"},
{network::mojom::WebClientHintsType::kUAArch, "sec-ch-ua-arch"},
{network::mojom::WebClientHintsType::kUAPlatform, "sec-ch-ua-platform"},
{network::mojom::WebClientHintsType::kUAModel, "sec-ch-ua-model"},
{network::mojom::WebClientHintsType::kUAMobile, "sec-ch-ua-mobile"},
{network::mojom::WebClientHintsType::kUAFullVersion,
"sec-ch-ua-full-version"},
{network::mojom::WebClientHintsType::kUAPlatformVersion,
"sec-ch-ua-platform-version"},
{network::mojom::WebClientHintsType::kPrefersColorScheme,
"sec-ch-prefers-color-scheme"},
{network::mojom::WebClientHintsType::kUABitness, "sec-ch-ua-bitness"},
{network::mojom::WebClientHintsType::kViewportHeight,
"sec-ch-viewport-height"},
{network::mojom::WebClientHintsType::kDeviceMemory,
"sec-ch-device-memory"},
{network::mojom::WebClientHintsType::kDpr, "sec-ch-dpr"},
{network::mojom::WebClientHintsType::kResourceWidth, "sec-ch-width"},
{network::mojom::WebClientHintsType::kViewportWidth,
"sec-ch-viewport-width"},
{network::mojom::WebClientHintsType::kUAFullVersionList,
"sec-ch-ua-full-version-list"},
{network::mojom::WebClientHintsType::kUAWoW64, "sec-ch-ua-wow64"},
{network::mojom::WebClientHintsType::kSaveData, "save-data"},
{network::mojom::WebClientHintsType::kPrefersReducedMotion,
"sec-ch-prefers-reduced-motion"},
{network::mojom::WebClientHintsType::kUAFormFactors,
"sec-ch-ua-form-factors"},
{network::mojom::WebClientHintsType::kPrefersReducedTransparency,
"sec-ch-prefers-reduced-transparency"},
};
}
const ClientHintToNameMap& GetClientHintToNameMap() {
static const base::NoDestructor<ClientHintToNameMap> map(
MakeClientHintToNameMap());
return *map;
}
namespace {
struct ClientHintNameComparator {
bool operator()(const std::string& lhs, const std::string& rhs) const {
return base::CompareCaseInsensitiveASCII(lhs, rhs) < 0;
}
};
using DecodeMap = base::flat_map<std::string,
network::mojom::WebClientHintsType,
ClientHintNameComparator>;
DecodeMap MakeDecodeMap() {
DecodeMap result;
for (const auto& elem : network::GetClientHintToNameMap()) {
const auto& type = elem.first;
const auto& header = elem.second;
result.insert(std::make_pair(header, type));
}
return result;
}
const DecodeMap& GetDecodeMap() {
static const base::NoDestructor<DecodeMap> decode_map(MakeDecodeMap());
return *decode_map;
}
} // namespace
std::optional<std::vector<network::mojom::WebClientHintsType>>
ParseClientHintsHeader(const std::string& header) {
// Accept-CH is an sh-list of tokens; see:
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-header-structure-19#section-3.1
std::optional<net::structured_headers::List> maybe_list =
net::structured_headers::ParseList(header);
if (!maybe_list.has_value())
return std::nullopt;
// Standard validation rules: we want a list of tokens, so this better
// only have tokens (but params are OK!)
for (const auto& list_item : maybe_list.value()) {
// Make sure not a nested list.
if (list_item.member.size() != 1u)
return std::nullopt;
if (!list_item.member[0].item.is_token())
return std::nullopt;
}
std::vector<network::mojom::WebClientHintsType> result;
// Now convert those to actual hint enums.
const DecodeMap& decode_map = GetDecodeMap();
for (const auto& list_item : maybe_list.value()) {
const std::string& token_value = list_item.member[0].item.GetString();
auto iter = decode_map.find(token_value);
if (iter != decode_map.end())
result.push_back(iter->second);
} // for list_item
return result;
}
ClientHintToDelegatedThirdPartiesHeader::
ClientHintToDelegatedThirdPartiesHeader() = default;
ClientHintToDelegatedThirdPartiesHeader::
~ClientHintToDelegatedThirdPartiesHeader() = default;
ClientHintToDelegatedThirdPartiesHeader::
ClientHintToDelegatedThirdPartiesHeader(
const ClientHintToDelegatedThirdPartiesHeader&) = default;
const ClientHintToDelegatedThirdPartiesHeader
ParseClientHintToDelegatedThirdPartiesHeader(const std::string& header,
MetaCHType type) {
const DecodeMap& decode_map = GetDecodeMap();
switch (type) {
case MetaCHType::HttpEquivAcceptCH: {
// ParseClientHintsHeader should have been called instead.
NOTREACHED_IN_MIGRATION();
return ClientHintToDelegatedThirdPartiesHeader();
}
case MetaCHType::HttpEquivDelegateCH: {
// We're building a scoped down version of
// ParsingContext::ParseFeaturePolicyToIR that supports only client hint
// features.
ClientHintToDelegatedThirdPartiesHeader result;
const auto& entries =
base::SplitString(base::ToLowerASCII(header), ";",
base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
for (const auto& entry : entries) {
const auto& components = base::SplitString(
entry, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
// Shouldn't be possible given this only processes non-empty parts.
DCHECK(components.size());
auto found_token = decode_map.find(components[0]);
// Bail early if the token is invalid
if (found_token == decode_map.end())
continue;
std::vector<url::Origin> delegates;
for (size_t i = 1; i < components.size(); ++i) {
const GURL gurl = GURL(components[i]);
if (!gurl.is_valid()) {
result.had_invalid_origins = true;
continue;
}
url::Origin origin = url::Origin::Create(gurl);
if (origin.opaque()) {
result.had_invalid_origins = true;
continue;
}
delegates.push_back(origin);
}
result.map.insert(std::make_pair(found_token->second, delegates));
}
return result;
}
}
}
// static
void LogClientHintsPersistenceMetrics(
const base::TimeTicks& persistence_started,
std::size_t hints_stored) {
base::UmaHistogramTimes("ClientHints.StoreLatency",
base::TimeTicks::Now() - persistence_started);
base::UmaHistogramExactLinear("ClientHints.UpdateEventCount", 1, 2);
base::UmaHistogramCounts100("ClientHints.UpdateSize", hints_stored);
}
} // namespace network