blob: a1bd473f03d8b2f36c99752177a560f7d06ec06e [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.h"
namespace blink {
namespace {
constexpr const char* kReportTo = "report-to";
constexpr const char* kNone = "none";
const char* ItemTypeToString(net::structured_headers::Item::ItemType type) {
switch (type) {
case net::structured_headers::Item::ItemType::kIntegerType:
return "Integer";
case net::structured_headers::Item::ItemType::kDecimalType:
return "Decimal";
case net::structured_headers::Item::ItemType::kBooleanType:
return "Boolean";
case net::structured_headers::Item::ItemType::kByteSequenceType:
return "ByteSequence";
case net::structured_headers::Item::ItemType::kNullType:
return "Null";
case net::structured_headers::Item::ItemType::kStringType:
return "String";
case net::structured_headers::Item::ItemType::kTokenType:
return "Token";
}
}
const char* PolicyValueTypeToString(mojom::blink::PolicyValueType type) {
switch (type) {
case mojom::blink::PolicyValueType::kNull:
return "Null";
case mojom::blink::PolicyValueType::kBool:
return "Boolean";
case mojom::blink::PolicyValueType::kDecDouble:
return "Double";
case mojom::blink::PolicyValueType::kEnum:
return "Enum";
}
}
base::Optional<PolicyValue> ItemToPolicyValue(
const net::structured_headers::Item& item,
mojom::blink::PolicyValueType type) {
switch (type) {
case mojom::blink::PolicyValueType::kBool: {
if (item.is_boolean()) {
return PolicyValue::CreateBool(item.GetBoolean());
} else {
return base::nullopt;
}
}
case mojom::blink::PolicyValueType::kDecDouble:
switch (item.Type()) {
case net::structured_headers::Item::ItemType::kIntegerType:
return PolicyValue::CreateDecDouble(
static_cast<double>(item.GetInteger()));
case net::structured_headers::Item::ItemType::kDecimalType:
return PolicyValue::CreateDecDouble(item.GetDecimal());
default:
return base::nullopt;
}
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;
};
base::Optional<ParsedFeature> ParseFeature(
const net::structured_headers::DictionaryMember& directive,
const DocumentPolicyNameFeatureMap& name_feature_map,
const DocumentPolicyFeatureInfoMap& feature_info_map,
PolicyParserMessageBuffer& logger) {
ParsedFeature parsed_feature;
const std::string& feature_name = directive.first;
if (directive.second.member_is_inner_list) {
logger.Warn(
String::Format("Parameter for feature %s should be single item, but "
"get list of items(length=%d).",
feature_name.c_str(),
static_cast<uint32_t>(directive.second.member.size())));
return base::nullopt;
}
// Parse feature_name string to DocumentPolicyFeature.
auto feature_iter = name_feature_map.find(feature_name);
if (feature_iter != name_feature_map.end()) {
parsed_feature.feature = feature_iter->second;
} else {
logger.Warn(String::Format("Unrecognized document policy feature name %s.",
feature_name.c_str()));
return base::nullopt;
}
auto expected_policy_value_type =
feature_info_map.at(parsed_feature.feature).default_value.Type();
const net::structured_headers::Item& item =
directive.second.member.front().item;
base::Optional<PolicyValue> policy_value =
ItemToPolicyValue(item, expected_policy_value_type);
if (!policy_value) {
logger.Warn(String::Format(
"Parameter for feature %s should be %s, not %s.", feature_name.c_str(),
PolicyValueTypeToString(expected_policy_value_type),
ItemTypeToString(item.Type())));
return base::nullopt;
}
parsed_feature.policy_value = *policy_value;
for (const auto& param : directive.second.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) {
logger.Warn(String::Format(
"\"report-to\" parameter should be a token in feature %s.",
feature_name.c_str()));
return base::nullopt;
}
} else {
// Unrecognized param.
logger.Warn(
String::Format("Unrecognized parameter name %s for feature %s.",
param_name.c_str(), feature_name.c_str()));
}
}
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,
PolicyParserMessageBuffer& logger) {
if (policy_string.IsEmpty())
return base::make_optional<DocumentPolicy::ParsedDocumentPolicy>({});
return ParseInternal(policy_string, GetDocumentPolicyNameFeatureMap(),
GetDocumentPolicyFeatureInfoMap(),
GetAvailableDocumentPolicyFeatures(), logger);
}
// 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,
PolicyParserMessageBuffer& logger) {
auto root = net::structured_headers::ParseDictionary(policy_string.Ascii());
if (!root) {
logger.Error(
"Parse of document policy failed because of errors reported by "
"structured header parser.");
return base::nullopt;
}
DocumentPolicy::ParsedDocumentPolicy parse_result;
std::string default_endpoint = "";
for (const net::structured_headers::DictionaryMember& directive :
root.value()) {
base::Optional<ParsedFeature> parsed_feature_option =
ParseFeature(directive, name_feature_map, feature_info_map, logger);
// If a feature fails parsing, ignore the entry.
if (!parsed_feature_option)
continue;
ParsedFeature parsed_feature = *parsed_feature_option;
if (parsed_feature.feature ==
mojom::blink::DocumentPolicyFeature::kDefault) {
if (parsed_feature.endpoint_group)
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