blob: 78d281a17d514290699907594bb00ec32dcd982b [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 "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/feature_policy/document_policy.h"
#include "third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom-blink.h"
namespace blink {
namespace {
constexpr const mojom::blink::DocumentPolicyFeature kBoolFeature =
static_cast<mojom::blink::DocumentPolicyFeature>(1);
constexpr const mojom::blink::DocumentPolicyFeature kDoubleFeature =
static_cast<mojom::blink::DocumentPolicyFeature>(2);
class DocumentPolicyParserTest : public ::testing::Test {
protected:
DocumentPolicyParserTest()
: name_feature_map(DocumentPolicyNameFeatureMap{
{"f-bool", kBoolFeature},
{"f-double", kDoubleFeature},
}),
feature_info_map(DocumentPolicyFeatureInfoMap{
{kBoolFeature, {"f-bool", "", PolicyValue(true)}},
{kDoubleFeature, {"f-double", "value", PolicyValue(1.0)}},
}) {
available_features.insert(kBoolFeature);
available_features.insert(kDoubleFeature);
}
~DocumentPolicyParserTest() override = default;
base::Optional<DocumentPolicy::ParsedDocumentPolicy> Parse(
const String& policy_string) {
return DocumentPolicyParser::ParseInternal(
policy_string, name_feature_map, feature_info_map, available_features);
}
base::Optional<std::string> Serialize(
const DocumentPolicy::FeatureState& policy) {
return DocumentPolicy::SerializeInternal(policy, feature_info_map);
}
private:
const DocumentPolicyNameFeatureMap name_feature_map;
const DocumentPolicyFeatureInfoMap feature_info_map;
DocumentPolicyFeatureSet available_features;
};
const char* const kValidPolicies[] = {
"", // An empty policy.
" ", // An empty policy.
"f-bool",
"no-f-bool",
"f-double;value=1.0",
"f-double;value=2",
"f-double;value=2.0,no-f-bool",
"no-f-bool,f-double;value=2.0",
"no-f-bool;report-to=default,f-double;value=2.0",
"no-f-bool;report-to=default,f-double;value=2.0;report-to=default",
"no-f-bool;report-to=default,f-double;report-to=default;value=2.0",
"no-f-bool;report-to=default,f-double;report-to=endpoint;value=2.0",
"no-f-bool,f-double;value=2.0;report-to=endpoint,*;report-to=default",
"*;report-to=default", // An empty policy.
"no-f-bool;report-to=none, f-double;value=2.0, *;report-to=default",
"no-f-bool;report-to=none, f-double;value=2.0, *;report-to=none",
"f-double;value=2;another_value=4", // excessive param should be
// acceptable.
};
const char* const kInvalidPolicies[] = {
"bad-feature-name", "no-bad-feature-name",
"f-double;value=?0", // wrong type of param
"\"f-bool\"", // policy member should be token instead of
// string
"();value=2", // empty feature token
"(f-bool f-double);value=2", // too many
// feature
// tokens
"f-double;report-to=default", // missing param
"f-bool;report-to=\"default\"", // report-to member should
// be token instead of
// string
};
const std::pair<DocumentPolicy::FeatureState, std::string>
kPolicySerializationTestCases[] = {
{{{kBoolFeature, PolicyValue(false)},
{kDoubleFeature, PolicyValue(1.0)}},
"no-f-bool, f-double;value=1.0"},
// Changing ordering of FeatureState element should not affect
// serialization result.
{{{kDoubleFeature, PolicyValue(1.0)},
{kBoolFeature, PolicyValue(false)}},
"no-f-bool, f-double;value=1.0"},
// Flipping boolean-valued policy from false to true should not affect
// result ordering of feature.
{{{kBoolFeature, PolicyValue(true)},
{kDoubleFeature, PolicyValue(1.0)}},
"f-bool, f-double;value=1.0"}};
const std::pair<const char*, DocumentPolicy::ParsedDocumentPolicy>
kPolicyParseTestCases[] = {
{"no-f-bool,f-double;value=1",
{{{kBoolFeature, PolicyValue(false)},
{kDoubleFeature, PolicyValue(1.0)}},
{} /* endpoint_map */}},
// White-space is allowed in some positions in structured-header.
{"no-f-bool, f-double;value=1",
{{{kBoolFeature, PolicyValue(false)},
{kDoubleFeature, PolicyValue(1.0)}},
{} /* endpoint_map */}},
// Unrecognized params are ignored for forwards compatibility.
{"no-f-bool,f-double;value=1;unknown_param=xxx",
{{{kBoolFeature, PolicyValue(false)},
{kDoubleFeature, PolicyValue(1.0)}},
{} /* endpoint_map */}},
{"no-f-bool,f-double;value=1;report-to=default",
{{{kBoolFeature, PolicyValue(false)},
{kDoubleFeature, PolicyValue(1.0)}},
{{kDoubleFeature, "default"}}}},
{"no-f-bool;report-to=default,f-double;value=1",
{{{kBoolFeature, PolicyValue(false)},
{kDoubleFeature, PolicyValue(1.0)}},
{{kBoolFeature, "default"}}}},
{"no-f-bool;report-to=none, f-double;value=2.0, *;report-to=default",
{/* feature_state */ {{kBoolFeature, PolicyValue(false)},
{kDoubleFeature, PolicyValue(2.0)}},
/* endpoint_map */ {{kDoubleFeature, "default"}}}},
{"no-f-bool;report-to=not_none, f-double;value=2.0, "
"*;report-to=default",
{/* feature_state */ {{kBoolFeature, PolicyValue(false)},
{kDoubleFeature, PolicyValue(2.0)}},
/* endpoint_map */ {{kBoolFeature, "not_none"},
{kDoubleFeature, "default"}}}},
{"no-f-bool;report-to=not_none, f-double;value=2.0, *;report-to=none",
{/* feature_state */ {{kBoolFeature, PolicyValue(false)},
{kDoubleFeature, PolicyValue(2.0)}},
/* endpoint_map */ {{kBoolFeature, "not_none"}}}},
// Default endpoint can be specified anywhere in the header, not
// necessary at the end.
{"no-f-bool;report-to=not_none, *;report-to=default, "
"f-double;value=2.0",
{/* feature_state */ {{kBoolFeature, PolicyValue(false)},
{kDoubleFeature, PolicyValue(2.0)}},
/* endpoint_map */ {{kBoolFeature, "not_none"},
{kDoubleFeature, "default"}}}},
// Default endpoint can be specified multiple times in the header.
// According to SH rules, last value wins.
{"no-f-bool;report-to=not_none, f-double;value=2.0, "
"*;report-to=default, "
"*;report-to=none",
{/* feature_state */ {{kBoolFeature, PolicyValue(false)},
{kDoubleFeature, PolicyValue(2.0)}},
/* endpoint_map */ {{kBoolFeature, "not_none"}}}},
// Even if default endpoint is not specified, none still should be
// treated as a reserved keyword for endpoint names.
{"no-f-bool;report-to=none",
{/* feature_state */ {{kBoolFeature, PolicyValue(false)}},
/* endpoint_map */ {}}},
};
const DocumentPolicy::FeatureState kParsedPolicies[] = {
{}, // An empty policy
{{kBoolFeature, PolicyValue(false)}},
{{kBoolFeature, PolicyValue(true)}},
{{kDoubleFeature, PolicyValue(1.0)}},
{{kBoolFeature, PolicyValue(true)}, {kDoubleFeature, PolicyValue(1.0)}}};
// Serialize and then Parse the result of serialization should cancel each
// other out, i.e. d == Parse(Serialize(d)).
// The other way s == Serialize(Parse(s)) is not always true because structured
// header allows some optional white spaces in its parsing targets and floating
// point numbers will be rounded, e.g. value=1 will be parsed to
// PolicyValue(1.0) and get serialized to value=1.0.
TEST_F(DocumentPolicyParserTest, SerializeAndParse) {
for (const auto& policy : kParsedPolicies) {
const base::Optional<std::string> policy_string = Serialize(policy);
ASSERT_TRUE(policy_string.has_value());
const base::Optional<DocumentPolicy::ParsedDocumentPolicy> reparsed_policy =
Parse(policy_string.value().c_str());
ASSERT_TRUE(reparsed_policy.has_value());
EXPECT_EQ(reparsed_policy.value().feature_state, policy);
}
}
TEST_F(DocumentPolicyParserTest, ParseValidPolicy) {
for (const char* policy : kValidPolicies) {
EXPECT_NE(Parse(policy), base::nullopt) << "Should parse " << policy;
}
}
TEST_F(DocumentPolicyParserTest, ParseInvalidPolicy) {
for (const char* policy : kInvalidPolicies) {
EXPECT_EQ(Parse(policy),
base::make_optional(DocumentPolicy::ParsedDocumentPolicy{}))
<< "Should fail to parse " << policy;
}
}
TEST_F(DocumentPolicyParserTest, SerializeResultShouldMatch) {
for (const auto& test_case : kPolicySerializationTestCases) {
const DocumentPolicy::FeatureState& policy = test_case.first;
const std::string& expected = test_case.second;
const auto result = Serialize(policy);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result.value(), expected);
}
}
TEST_F(DocumentPolicyParserTest, ParseResultShouldMatch) {
for (const auto& test_case : kPolicyParseTestCases) {
const char* input = test_case.first;
const DocumentPolicy::ParsedDocumentPolicy& expected = test_case.second;
const auto result = Parse(input);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result.value(), expected);
}
}
} // namespace
} // namespace blink