blob: 0bdb03eadeac25ee94ce47ddf7b3fc46cee7653b [file] [log] [blame]
// Copyright 2025 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/integrity_policy_parser.h"
#include "base/strings/cstring_view.h"
#include "base/strings/stringprintf.h"
#include "net/http/http_response_headers.h"
#include "net/http/structured_headers.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/integrity_policy.h"
namespace network {
namespace {
void HandleKeyValue(base::cstring_view key,
const net::structured_headers::ParameterizedMember& value,
base::cstring_view header_name,
IntegrityPolicy& policy) {
if (!value.member_is_inner_list) {
policy.parsing_errors.emplace_back(
base::StringPrintf("The %s value is not a list.", header_name.c_str()));
return;
}
for (const auto& parameter : value.member) {
if (!parameter.item.is_token()) {
policy.parsing_errors.emplace_back(base::StringPrintf(
"The %s item '%s' is not a token. "
"Did you accidentally add it as a string?",
header_name.c_str(), parameter.item.GetString().c_str()));
continue;
}
const base::cstring_view parameter_value = parameter.item.GetString();
if (key == "blocked-destinations") {
if (parameter_value == "script") {
policy.blocked_destinations.emplace_back(
mojom::IntegrityPolicy_Destination::kScript);
} else {
policy.parsing_errors.emplace_back(
base::StringPrintf("The %s destination '%s' is not supported.",
header_name.c_str(), parameter_value.c_str()));
}
} else if (key == "sources") {
if (parameter_value == "inline") {
policy.sources.emplace_back(mojom::IntegrityPolicy_Source::kInline);
} else {
policy.parsing_errors.emplace_back(
base::StringPrintf("The %s source '%s' is not supported.",
header_name.c_str(), parameter_value.c_str()));
}
} else if (key == "endpoints") {
policy.endpoints.emplace_back(parameter_value);
} else {
policy.parsing_errors.emplace_back(base::StringPrintf(
"Unrecognized %s in %s header.", key.c_str(), header_name.c_str()));
}
}
}
} // namespace
IntegrityPolicy ParseIntegrityPolicyFromHeaders(
const net::HttpResponseHeaders& headers,
IntegrityPolicyHeaderType type) {
CHECK(
base::FeatureList::IsEnabled(network::features::kIntegrityPolicyScript));
IntegrityPolicy parsed_policy;
const std::string header_name = (type == IntegrityPolicyHeaderType::kEnforce)
? "Integrity-Policy"
: "Integrity-Policy-Report-Only";
const std::string integrity_policy_header =
headers.GetNormalizedHeader(header_name).value_or("");
if (integrity_policy_header.empty()) {
return parsed_policy;
}
std::optional<net::structured_headers::Dictionary>
integrity_policy_dictionary =
net::structured_headers::ParseDictionary(integrity_policy_header);
if (!integrity_policy_dictionary) {
parsed_policy.parsing_errors.emplace_back(base::StringPrintf(
"The %s value \"%s\" is not a dictionary.", header_name.c_str(),
integrity_policy_header.c_str()));
return parsed_policy;
}
bool has_sources_key = false;
// Loop through the policy dictionary
//
// https://datatracker.ietf.org/doc/html/rfc9421#section-4-4
for (const net::structured_headers::DictionaryMember& policy_entry :
integrity_policy_dictionary.value()) {
if (policy_entry.first == "sources") {
has_sources_key = true;
}
HandleKeyValue(policy_entry.first, policy_entry.second, header_name,
parsed_policy);
}
if (!has_sources_key) {
// If the `sources` key is missing from the header, add "inline" as the
// default. That would enable us to add future integrity metadata sources,
// without forcing developers to explicitly add the source today, when only
// one integrity metadata source exists.
parsed_policy.sources.emplace_back(mojom::IntegrityPolicy_Source::kInline);
}
return parsed_policy;
}
} // namespace network