blob: 55cd1f4ac7780427f218e8905c17c0a976394ae1 [file] [log] [blame]
// Copyright 2020 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 "third_party/blink/renderer/core/feature_policy/document_policy_parser.h"
#include "net/http/structured_headers.h"
#include "third_party/blink/public/mojom/feature_policy/policy_value.mojom-blink-forward.h"
namespace blink {
namespace {
constexpr const char* kReportTo = "report-to";
constexpr const char* kNone = "none";
constexpr const char* kWildCard = "*";
base::Optional<PolicyValue> ItemToPolicyValue(
const net::structured_headers::Item& item) {
switch (item.Type()) {
case net::structured_headers::Item::ItemType::kIntegerType:
return PolicyValue(static_cast<double>(item.GetInteger()));
case net::structured_headers::Item::ItemType::kDecimalType:
return PolicyValue(item.GetDecimal());
default:
return base::nullopt;
}
}
base::Optional<std::string> ItemToString(
const net::structured_headers::Item& item) {
if (item.Type() != net::structured_headers::Item::ItemType::kTokenType)
return base::nullopt;
return item.GetString();
}
struct ParsedFeature {
mojom::blink::DocumentPolicyFeature feature;
PolicyValue policy_value;
base::Optional<std::string> endpoint_group;
// Wildcard feature('*') is used to specify default endpoint for features.
bool is_wildcard = false;
};
base::Optional<ParsedFeature> ParseWildcardFeature(
const net::structured_headers::ParameterizedMember& directive) {
base::Optional<std::string> endpoint_group;
for (const auto& param : directive.params) {
if (param.first == kReportTo) {
endpoint_group = ItemToString(param.second);
if (!endpoint_group)
return base::nullopt;
}
}
return base::make_optional<ParsedFeature>(
{mojom::blink::DocumentPolicyFeature::kDefault, PolicyValue(),
endpoint_group, true});
}
base::Optional<ParsedFeature> ParseFeature(
const net::structured_headers::ParameterizedMember& directive,
const DocumentPolicyNameFeatureMap& name_feature_map,
const DocumentPolicyFeatureInfoMap& feature_info_map) {
ParsedFeature parsed_feature;
// Directives must not be inner lists.
if (directive.member_is_inner_list)
return base::nullopt;
const net::structured_headers::Item& feature_token =
directive.member.front().item;
// The item in directive should be token type.
if (!feature_token.is_token())
return base::nullopt;
std::string feature_name = feature_token.GetString();
if (feature_name == kWildCard)
return ParseWildcardFeature(directive);
auto feature_iter = name_feature_map.find(feature_name);
// Parse feature_name string to DocumentPolicyFeature.
if (feature_iter != name_feature_map.end()) {
parsed_feature.feature = feature_iter->second;
} else if (feature_name.size() > 3 && feature_name.substr(0, 3) == "no-") {
// Handle "no-" prefix.
feature_iter = name_feature_map.find(feature_name.substr(3));
if (feature_iter != name_feature_map.end()) {
parsed_feature.feature = feature_iter->second;
// "no-" prefix is exclusively for policy with Boolean policy value.
if (feature_info_map.at(parsed_feature.feature).default_value.Type() !=
mojom::blink::PolicyValueType::kBool)
return base::nullopt;
parsed_feature.policy_value = PolicyValue(false);
} else {
return base::nullopt; // Unrecognized feature name.
}
} else {
return base::nullopt; // Unrecognized feature name.
}
// Handle boolean value.
// For document policy that has a boolean policy value, policy value is not
// specified as directive param. Instead, the value is expressed using "no-"
// prefix, e.g. for feature X, "X" itself in header should be parsed as true,
// "no-X" should be parsed as false.
if (feature_info_map.at(parsed_feature.feature).default_value.Type() ==
mojom::blink::PolicyValueType::kBool &&
parsed_feature.policy_value.Type() ==
mojom::blink::PolicyValueType::kNull)
parsed_feature.policy_value = PolicyValue(true);
const std::string& feature_param_name =
feature_info_map.at(parsed_feature.feature).feature_param_name;
for (const auto& param : directive.params) {
const std::string& param_name = param.first;
// Handle "report-to" param. "report-to" is an optional param for
// Document-Policy header that specifies the endpoint group that the policy
// should send report to. If left unspecified, no report will be send upon
// policy violation.
if (param_name == kReportTo) {
parsed_feature.endpoint_group = ItemToString(param.second);
if (!parsed_feature.endpoint_group)
return base::nullopt;
} else if (param_name == feature_param_name) {
// Handle policy value. For all non-boolean policy value types, they
// should be specified as FeatureX;f=xxx, with f representing the
// |feature_param_name| and xxx representing policy value.
// |parsed_feature.policy_value| should not be assigned yet.
DCHECK(parsed_feature.policy_value.Type() ==
mojom::blink::PolicyValueType::kNull);
base::Optional<PolicyValue> policy_value =
ItemToPolicyValue(param.second);
if (!policy_value)
return base::nullopt;
parsed_feature.policy_value = *policy_value;
}
}
// |parsed_feature.policy_value| should be initialized.
if (parsed_feature.policy_value.Type() ==
mojom::blink::PolicyValueType::kNull)
return base::nullopt;
return parsed_feature;
}
// Apply |default_endpoint| to given |parsed_policy|.
void ApplyDefaultEndpoint(DocumentPolicy::ParsedDocumentPolicy& parsed_policy,
const std::string& default_endpoint) {
DocumentPolicy::FeatureEndpointMap& endpoint_map = parsed_policy.endpoint_map;
if (!default_endpoint.empty()) {
// Fill |default_endpoint| to all feature entry whose |endpoint_group|
// is missing.
for (const auto& feature_and_value : parsed_policy.feature_state) {
mojom::blink::DocumentPolicyFeature feature = feature_and_value.first;
if (endpoint_map.find(feature) == endpoint_map.end())
endpoint_map.emplace(feature, default_endpoint);
}
}
// Remove |endpoint_group| for feature entry if its |endpoint_group|
// is "none".
// Note: if |default_endpoint| is "none", all "none" items are filtered out
// here. it would be equivalent to doing nothing.
for (auto iter = endpoint_map.begin(); iter != endpoint_map.end();) {
if (iter->second == kNone) {
iter = endpoint_map.erase(iter);
} else {
++iter;
}
}
}
} // namespace
// static
base::Optional<DocumentPolicy::ParsedDocumentPolicy>
DocumentPolicyParser::Parse(const String& policy_string) {
if (policy_string.IsEmpty())
return base::make_optional<DocumentPolicy::ParsedDocumentPolicy>({});
return ParseInternal(policy_string, GetDocumentPolicyNameFeatureMap(),
GetDocumentPolicyFeatureInfoMap(),
GetAvailableDocumentPolicyFeatures());
}
// static
base::Optional<DocumentPolicy::ParsedDocumentPolicy>
DocumentPolicyParser::ParseInternal(
const String& policy_string,
const DocumentPolicyNameFeatureMap& name_feature_map,
const DocumentPolicyFeatureInfoMap& feature_info_map,
const DocumentPolicyFeatureSet& available_features) {
auto root = net::structured_headers::ParseList(policy_string.Ascii());
if (!root)
return base::nullopt;
DocumentPolicy::ParsedDocumentPolicy parse_result;
std::string default_endpoint = "";
for (const net::structured_headers::ParameterizedMember& directive :
root.value()) {
base::Optional<ParsedFeature> parsed_feature_option =
ParseFeature(directive, name_feature_map, feature_info_map);
// If a feature fails parsing, ignore the entry.
if (!parsed_feature_option)
continue;
ParsedFeature parsed_feature = *parsed_feature_option;
if (parsed_feature.is_wildcard) {
default_endpoint = *parsed_feature.endpoint_group;
continue;
}
// If feature is not available, i.e. not enabled, ignore the entry.
if (available_features.find(parsed_feature.feature) ==
available_features.end())
continue;
parse_result.feature_state.emplace(parsed_feature.feature,
std::move(parsed_feature.policy_value));
if (parsed_feature.endpoint_group) {
parse_result.endpoint_map.emplace(parsed_feature.feature,
*parsed_feature.endpoint_group);
}
}
ApplyDefaultEndpoint(parse_result, default_endpoint);
return parse_result;
}
} // namespace blink