blob: f1bcd7e8b62a840a72fd505785825088fce2b50c [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 <vector>
#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 {
constexpr const mojom::blink::DocumentPolicyFeature kDefault =
mojom::blink::DocumentPolicyFeature::kDefault;
constexpr const mojom::blink::DocumentPolicyFeature kBoolFeature =
static_cast<mojom::blink::DocumentPolicyFeature>(1);
constexpr const mojom::blink::DocumentPolicyFeature kDoubleFeature =
static_cast<mojom::blink::DocumentPolicyFeature>(2);
// This is the test version of |PolicyParserMessageBuffer::Message| as
// WTF::String cannot be statically allocated.
struct MessageForTest {
mojom::ConsoleMessageLevel level;
const char* content;
};
struct ParseTestCase {
const char* test_name;
const char* input_string;
DocumentPolicy::ParsedDocumentPolicy parsed_policy;
std::vector<MessageForTest> messages;
};
class DocumentPolicyParserTest
: public ::testing::TestWithParam<ParseTestCase> {
protected:
DocumentPolicyParserTest()
: name_feature_map(DocumentPolicyNameFeatureMap{
{"*", kDefault},
{"f-bool", kBoolFeature},
{"f-double", kDoubleFeature},
}),
feature_info_map(DocumentPolicyFeatureInfoMap{
{kDefault, {"*", PolicyValue::CreateBool(true)}},
{kBoolFeature, {"f-bool", PolicyValue::CreateBool(true)}},
{kDoubleFeature, {"f-double", PolicyValue::CreateDecDouble(1.0)}},
}) {
available_features.insert(kBoolFeature);
available_features.insert(kDoubleFeature);
}
~DocumentPolicyParserTest() override = default;
base::Optional<DocumentPolicy::ParsedDocumentPolicy> Parse(
const String& policy_string,
PolicyParserMessageBuffer& logger) {
return DocumentPolicyParser::ParseInternal(policy_string, name_feature_map,
feature_info_map,
available_features, logger);
}
base::Optional<std::string> Serialize(
const DocumentPolicyFeatureState& policy) {
return DocumentPolicy::SerializeInternal(policy, feature_info_map);
}
private:
const DocumentPolicyNameFeatureMap name_feature_map;
const DocumentPolicyFeatureInfoMap feature_info_map;
DocumentPolicyFeatureSet available_features;
public:
static const ParseTestCase kCases[];
};
const ParseTestCase DocumentPolicyParserTest::kCases[] = {
//
// Parse valid policy strings.
//
{
"ParseEmptyPolicyString",
"",
/* parsed_policy */
{
/* feature_state */ {},
/* endpoint_map */ {},
},
/* messages */ {},
},
{
"ParseWhitespaceOnlyString",
" ",
/* parsed_policy */
{
/* feature_state */ {},
/* endpoint_map */ {},
},
/* messages */ {},
},
{
"ParseBoolFeatureWithValueTrue",
"f-bool",
/* parsed_policy */
{
/* feature_state */ {{kBoolFeature, PolicyValue::CreateBool(true)}},
/* endpoint_map */ {},
},
/* messages */ {},
},
{
"ParseBoolFeatureWithValueFalse",
"f-bool=?0",
/* parsed_policy */
{
/* feature_state */ {
{kBoolFeature, PolicyValue::CreateBool(false)}},
/* endpoint_map */ {},
},
/* messages */ {},
},
{
"ParseDoubleFeature1",
"f-double=1.0",
/* parsed_policy */
{
/* feature_state */ {
{kDoubleFeature, PolicyValue::CreateDecDouble(1.0)}},
/* endpoint_map */ {},
},
/* messages */ {},
},
{
"ParseDoubleFeature2",
"f-double=2",
/* parsed_policy */
{
/* feature_state */ {
{kDoubleFeature, PolicyValue::CreateDecDouble(2.0)}},
/* endpoint_map */ {},
},
/* messages */ {},
},
{
"ParseDoubleFeatureAndBoolFeature",
"f-double=1,f-bool=?0",
/* parsed_policy */
{
/* feature_state */ {
{kBoolFeature, PolicyValue::CreateBool(false)},
{kDoubleFeature, PolicyValue::CreateDecDouble(1.0)}},
/* endpoint_map */ {},
},
/* messages */ {},
},
{
"ParseBoolFeatureAndDoubleFeature",
"f-bool=?0,f-double=1",
/* parsed_policy */
{
/* feature_state */ {
{kBoolFeature, PolicyValue::CreateBool(false)},
{kDoubleFeature, PolicyValue::CreateDecDouble(1.0)}},
/* endpoint_map */ {},
},
/* messages */ {},
},
{
"WhitespaceIsAllowedInSomePositionsInStructuredHeader",
"f-bool=?0, f-double=1",
/* parsed_policy */
{/* feature_state */ {
{kBoolFeature, PolicyValue::CreateBool(false)},
{kDoubleFeature, PolicyValue::CreateDecDouble(1.0)}},
/* endpoint_map */ {}},
/* messages */ {},
},
{
"UnrecognizedParametersAreIgnoredButTheFeatureEntryShould"
"RemainValid",
"f-bool=?0,f-double=1;unknown_param=xxx",
/* parsed_policy */
{/* feature_state */ {
{kBoolFeature, PolicyValue::CreateBool(false)},
{kDoubleFeature, PolicyValue::CreateDecDouble(1.0)}},
/* endpoint_map */ {}},
/* messages */
{{mojom::blink::ConsoleMessageLevel::kWarning,
"Unrecognized parameter name unknown_param for feature f-double."}},
},
{
"ParsePolicyWithReportEndpointSpecified1",
"f-bool=?0,f-double=1;report-to=default",
/* parsed_policy */
{/* feature_state */ {
{kBoolFeature, PolicyValue::CreateBool(false)},
{kDoubleFeature, PolicyValue::CreateDecDouble(1.0)}},
/* endpoint_map */ {{kDoubleFeature, "default"}}},
/* messages */ {},
},
{
"ParsePolicyWithReportEndpointSpecified2",
"f-bool=?0;report-to=default,f-double=1",
/* parsed_policy */
{/* feature_state */ {
{kBoolFeature, PolicyValue::CreateBool(false)},
{kDoubleFeature, PolicyValue::CreateDecDouble(1.0)}},
/* endpoint_map */ {{kBoolFeature, "default"}}},
/* messages */ {},
},
{
"ParsePolicyWithDefaultReportEndpointAndNone"
"KeywordShouldOverwriteDefaultValue",
"f-bool=?0;report-to=none, f-double=2.0, *;report-to=default",
/* parsed_policy */
{/* feature_state */ {
{kBoolFeature, PolicyValue::CreateBool(false)},
{kDoubleFeature, PolicyValue::CreateDecDouble(2.0)}},
/* endpoint_map */ {{kDoubleFeature, "default"}}},
/* messages */ {},
},
{
"ParsePolicyWithDefaultReportEndpointSpecified",
"f-bool=?0;report-to=not_none, f-double=2.0, "
"*;report-to=default",
/* parsed_policy */
{/* feature_state */ {
{kBoolFeature, PolicyValue::CreateBool(false)},
{kDoubleFeature, PolicyValue::CreateDecDouble(2.0)}},
/* endpoint_map */ {{kBoolFeature, "not_none"},
{kDoubleFeature, "default"}}},
/* messages */ {},
},
{
"ParsePolicyWithDefaultReportEndpointSpecifiedAsNone",
"f-bool=?0;report-to=not_none, f-double=2.0, *;report-to=none",
/* parsed_policy */
{/* feature_state */ {
{kBoolFeature, PolicyValue::CreateBool(false)},
{kDoubleFeature, PolicyValue::CreateDecDouble(2.0)}},
/* endpoint_map */ {{kBoolFeature, "not_none"}}},
/* messages */ {},
},
{
"DefaultEndpointCanBeSpecifiedAnywhereInTheHeader",
"f-bool=?0;report-to=not_none, *;report-to=default, "
"f-double=2.0",
/* parsed_policy */
{/* feature_state */ {
{kBoolFeature, PolicyValue::CreateBool(false)},
{kDoubleFeature, PolicyValue::CreateDecDouble(2.0)}},
/* endpoint_map */ {{kBoolFeature, "not_none"},
{kDoubleFeature, "default"}}},
/* messages */ {},
},
{
"DefaultEndpointCanBeSpecifiedMultipleTimesInTheHeader",
"f-bool=?0;report-to=not_none, f-double=2.0, "
"*;report-to=default, *;report-to=none",
/* parsed_policy */
{/* feature_state */ {
{kBoolFeature, PolicyValue::CreateBool(false)},
{kDoubleFeature, PolicyValue::CreateDecDouble(2.0)}},
/* endpoint_map */ {{kBoolFeature, "not_none"}}},
/* messages */ {},
},
{
"EvenIfDefaultEndpointIsNotSpecifiedNoneStillShouldBe"
"TreatedAsReservedKeywordForEndpointNames",
"f-bool=?0;report-to=none",
/* parsed_policy */
{/* feature_state */ {{kBoolFeature, PolicyValue::CreateBool(false)}},
/* endpoint_map */ {}},
/* messages */ {},
},
//
// Parse invalid policies.
//
{
"ParsePolicyWithUnrecognizedFeatureName1",
"bad-feature-name",
/* parsed_policy */
{
/* feature_state */ {},
/* endpoint_map */ {},
},
/* messages */
{{mojom::blink::ConsoleMessageLevel::kWarning,
"Unrecognized document policy feature name "
"bad-feature-name."}},
},
{
"ParsePolicyWithUnrecognizedFeatureName2",
"no-bad-feature-name",
/* parsed_policy */
{
/* feature_state */ {},
/* endpoint_map */ {},
},
/* messages */
{{mojom::blink::ConsoleMessageLevel::kWarning,
"Unrecognized document policy feature name "
"no-bad-feature-name."}},
},
{
"ParsePolicyWithWrongTypeOfParamExpectedDoubleTypeButGet"
"BooleanType",
"f-double=?0",
/* parsed_policy */
{
/* feature_state */ {},
/* endpoint_map */ {},
},
/* messages */
{{mojom::blink::ConsoleMessageLevel::kWarning,
"Parameter for feature f-double should be Double, not "
"Boolean."}},
},
{
"ParsePolicyWithWrongTypeOfParamExpectedBooleanTypeButGet"
"DoubleType",
"f-bool=1.0",
/* parsed_policy */
{
/* feature_state */ {},
/* endpoint_map */ {},
},
/* messages */
{{mojom::blink::ConsoleMessageLevel::kWarning,
"Parameter for feature f-bool should be Boolean, not "
"Decimal."}},
},
{
"FeatureValueItemShouldNotBeEmpty",
"f-double=()",
/* parsed_policy */
{
/* feature_state */ {},
/* endpoint_map */ {},
},
/* messages */
{{mojom::blink::ConsoleMessageLevel::kWarning,
"Parameter for feature f-double should be single item, but get list "
"of items(length=0)."}},
},
{
"TooManyFeatureValueItems",
"f-double=(1.1 2.0)",
/* parsed_policy */
{
/* feature_state */ {},
/* endpoint_map */ {},
},
/* messages */
{{mojom::blink::ConsoleMessageLevel::kWarning,
"Parameter for feature f-double should be single item, but get list "
"of items(length=2)."}},
},
{
"ReportToParameterValueTypeShouldBeTokenInsteadOf"
"String",
"f-bool;report-to=\"default\"",
/* parsed_policy */
{
/* feature_state */ {},
/* endpoint_map */ {},
},
/* messages */
{{mojom::blink::ConsoleMessageLevel::kWarning,
"\"report-to\" parameter should be a token in feature f-bool."}},
},
};
const std::pair<DocumentPolicyFeatureState, std::string>
kPolicySerializationTestCases[] = {
{{{kBoolFeature, PolicyValue::CreateBool(false)},
{kDoubleFeature, PolicyValue::CreateDecDouble(1.0)}},
"f-bool=?0, f-double=1.0"},
// Changing ordering of FeatureState element should not affect
// serialization result.
{{{kDoubleFeature, PolicyValue::CreateDecDouble(1.0)},
{kBoolFeature, PolicyValue::CreateBool(false)}},
"f-bool=?0, f-double=1.0"},
// Flipping boolean-valued policy from false to true should not affect
// result ordering of feature.
{{{kBoolFeature, PolicyValue::CreateBool(true)},
{kDoubleFeature, PolicyValue::CreateDecDouble(1.0)}},
"f-bool, f-double=1.0"}};
const DocumentPolicyFeatureState kParsedPolicies[] = {
{}, // An empty policy
{{kBoolFeature, PolicyValue::CreateBool(false)}},
{{kBoolFeature, PolicyValue::CreateBool(true)}},
{{kDoubleFeature, PolicyValue::CreateDecDouble(1.0)}},
{{kBoolFeature, PolicyValue::CreateBool(true)},
{kDoubleFeature, PolicyValue::CreateDecDouble(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::CreateDecDouble(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());
PolicyParserMessageBuffer logger;
const base::Optional<DocumentPolicy::ParsedDocumentPolicy> reparsed_policy =
Parse(policy_string.value().c_str(), logger);
ASSERT_TRUE(reparsed_policy.has_value());
EXPECT_EQ(reparsed_policy.value().feature_state, policy);
}
}
TEST_F(DocumentPolicyParserTest, SerializeResultShouldMatch) {
for (const auto& test_case : kPolicySerializationTestCases) {
const DocumentPolicyFeatureState& 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);
}
}
INSTANTIATE_TEST_SUITE_P(
All,
DocumentPolicyParserTest,
::testing::ValuesIn(DocumentPolicyParserTest::kCases),
[](const ::testing::TestParamInfo<ParseTestCase>& param_info) {
return param_info.param.test_name;
});
TEST_P(DocumentPolicyParserTest, ParseResultShouldMatch) {
const ParseTestCase& test_case = GetParam();
PolicyParserMessageBuffer logger;
const auto result = Parse(test_case.input_string, logger);
// All tese cases should not return base::nullopt because they all comply to
// structured header syntax.
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->endpoint_map, test_case.parsed_policy.endpoint_map)
<< "\n endpoint map should match";
EXPECT_EQ(result->feature_state, test_case.parsed_policy.feature_state)
<< "\n feature state should match";
EXPECT_EQ(logger.GetMessages().size(), test_case.messages.size())
<< "\n messages length should match";
const auto& actual_messages = logger.GetMessages();
const std::vector<MessageForTest>& expected_messages = test_case.messages;
ASSERT_EQ(actual_messages.size(), expected_messages.size())
<< "message count should match";
for (size_t i = 0; i < expected_messages.size(); ++i) {
const auto& actual_message = actual_messages[i];
const MessageForTest& expected_message = expected_messages[i];
EXPECT_EQ(actual_message.level, expected_message.level)
<< "\n message level should match";
EXPECT_EQ(actual_message.content, String(expected_message.content))
<< "\n message content should match";
}
}
} // namespace blink