blob: 5749caaae4060d68e89b9758ac4b43ae4fbb2023 [file] [log] [blame]
// Copyright 2019 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/public/common/feature_policy/document_policy.h"
#include "base/memory/ptr_util.h"
#include "base/no_destructor.h"
#include "net/http/structured_headers.h"
#include "third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom.h"
namespace blink {
// static
std::unique_ptr<DocumentPolicy> DocumentPolicy::CreateWithHeaderPolicy(
const ParsedDocumentPolicy& header_policy) {
DocumentPolicyFeatureState feature_defaults;
for (const auto& entry : GetDocumentPolicyFeatureInfoMap())
feature_defaults.emplace(entry.first, entry.second.default_value);
return CreateWithHeaderPolicy(header_policy.feature_state,
header_policy.endpoint_map, feature_defaults);
}
// static
std::unique_ptr<DocumentPolicy> DocumentPolicy::CopyStateFrom(
const DocumentPolicy* source) {
if (!source)
return nullptr;
std::unique_ptr<DocumentPolicy> new_policy =
DocumentPolicy::CreateWithHeaderPolicy(
{/* header_policy */ {}, /* endpoint_map */ {}});
new_policy->internal_feature_state_ = source->internal_feature_state_;
new_policy->endpoint_map_ = source->endpoint_map_;
return new_policy;
}
namespace {
net::structured_headers::Item PolicyValueToItem(const PolicyValue& value) {
switch (value.Type()) {
case mojom::PolicyValueType::kBool:
return net::structured_headers::Item{value.BoolValue()};
case mojom::PolicyValueType::kDecDouble:
return net::structured_headers::Item{value.DoubleValue()};
default:
NOTREACHED();
return net::structured_headers::Item{
nullptr, net::structured_headers::Item::ItemType::kNullType};
}
}
} // namespace
// static
base::Optional<std::string> DocumentPolicy::Serialize(
const DocumentPolicyFeatureState& policy) {
return DocumentPolicy::SerializeInternal(policy,
GetDocumentPolicyFeatureInfoMap());
}
// static
base::Optional<std::string> DocumentPolicy::SerializeInternal(
const DocumentPolicyFeatureState& policy,
const DocumentPolicyFeatureInfoMap& feature_info_map) {
net::structured_headers::Dictionary root;
std::vector<std::pair<mojom::DocumentPolicyFeature, PolicyValue>>
sorted_policy(policy.begin(), policy.end());
std::sort(sorted_policy.begin(), sorted_policy.end(),
[&](const auto& a, const auto& b) {
const std::string& feature_a =
feature_info_map.at(a.first).feature_name;
const std::string& feature_b =
feature_info_map.at(b.first).feature_name;
return feature_a < feature_b;
});
for (const auto& policy_entry : sorted_policy) {
const mojom::DocumentPolicyFeature feature = policy_entry.first;
const std::string& feature_name = feature_info_map.at(feature).feature_name;
const PolicyValue& value = policy_entry.second;
root[feature_name] = net::structured_headers::ParameterizedMember(
PolicyValueToItem(value), /* parameters */ {});
}
return net::structured_headers::SerializeDictionary(root);
}
// static
DocumentPolicyFeatureState DocumentPolicy::MergeFeatureState(
const DocumentPolicyFeatureState& base_policy,
const DocumentPolicyFeatureState& override_policy) {
DocumentPolicyFeatureState result;
auto i1 = base_policy.begin();
auto i2 = override_policy.begin();
// Because std::map is by default ordered in ascending order based on key
// value, we can run 2 iterators simultaneously through both maps to merge
// them.
while (i1 != base_policy.end() || i2 != override_policy.end()) {
if (i1 == base_policy.end()) {
result.insert(*i2);
i2++;
} else if (i2 == override_policy.end()) {
result.insert(*i1);
i1++;
} else {
if (i1->first == i2->first) {
const PolicyValue& base_value = i1->second;
const PolicyValue& override_value = i2->second;
// When policy value has strictness ordering e.g. boolean, take the
// stricter one. In this case a.IsCompatibleWith(b) means a is eq or
// stricter than b.
// When policy value does not have strictness ordering, e.g. enum,
// take override_value. In this case a.IsCompatibleWith(b) means
// a != b.
const PolicyValue& new_value =
base_value.IsCompatibleWith(override_value) ? base_value
: override_value;
result.emplace(i1->first, new_value);
i1++;
i2++;
} else if (i1->first < i2->first) {
result.insert(*i1);
i1++;
} else {
result.insert(*i2);
i2++;
}
}
}
return result;
}
bool DocumentPolicy::IsFeatureEnabled(
mojom::DocumentPolicyFeature feature) const {
mojom::PolicyValueType feature_type =
GetDocumentPolicyFeatureInfoMap().at(feature).default_value.Type();
return IsFeatureEnabled(feature,
PolicyValue::CreateMaxPolicyValue(feature_type));
}
bool DocumentPolicy::IsFeatureEnabled(
mojom::DocumentPolicyFeature feature,
const PolicyValue& threshold_value) const {
return threshold_value.IsCompatibleWith(GetFeatureValue(feature));
}
PolicyValue DocumentPolicy::GetFeatureValue(
mojom::DocumentPolicyFeature feature) const {
return internal_feature_state_[static_cast<size_t>(feature)];
}
const base::Optional<std::string> DocumentPolicy::GetFeatureEndpoint(
mojom::DocumentPolicyFeature feature) const {
auto endpoint_it = endpoint_map_.find(feature);
if (endpoint_it != endpoint_map_.end()) {
return endpoint_it->second;
} else {
return base::nullopt;
}
}
void DocumentPolicy::UpdateFeatureState(
const DocumentPolicyFeatureState& feature_state) {
for (const auto& feature_and_value : feature_state) {
internal_feature_state_[static_cast<size_t>(feature_and_value.first)] =
feature_and_value.second;
}
}
DocumentPolicy::DocumentPolicy(const DocumentPolicyFeatureState& header_policy,
const FeatureEndpointMap& endpoint_map,
const DocumentPolicyFeatureState& defaults)
: endpoint_map_(endpoint_map) {
// Fill the internal feature state with default value first,
// and overwrite the value if it is specified in the header.
UpdateFeatureState(defaults);
UpdateFeatureState(header_policy);
}
// static
std::unique_ptr<DocumentPolicy> DocumentPolicy::CreateWithHeaderPolicy(
const DocumentPolicyFeatureState& header_policy,
const FeatureEndpointMap& endpoint_map,
const DocumentPolicyFeatureState& defaults) {
std::unique_ptr<DocumentPolicy> new_policy = base::WrapUnique(
new DocumentPolicy(header_policy, endpoint_map, defaults));
return new_policy;
}
// static
bool DocumentPolicy::IsPolicyCompatible(
const DocumentPolicyFeatureState& required_policy,
const DocumentPolicyFeatureState& incoming_policy) {
for (const auto& required_entry : required_policy) {
const auto& feature = required_entry.first;
const auto& required_value = required_entry.second;
// Use default value when incoming policy does not specify a value.
const auto incoming_entry = incoming_policy.find(feature);
const auto& incoming_value =
incoming_entry != incoming_policy.end()
? incoming_entry->second
: GetDocumentPolicyFeatureInfoMap().at(feature).default_value;
if (!incoming_value.IsCompatibleWith(required_value))
return false;
}
return true;
}
} // namespace blink