Commit 3a524069 authored by Charlie Hu's avatar Charlie Hu Committed by Commit Bot

Add error/warning message output for DocumentPolicyParser

Previously when document policy is parsed, parsing errors are silent,
which can cause difficulties in debugging. This CL adds parsing
error output for DocumentPolicyParser.

Bug: 993790
Change-Id: I8e7f88abf73990793a456f3f6a39bf4054d4b6b1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2136186
Commit-Queue: Charlie Hu <chenleihu@google.com>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarIan Clelland <iclelland@chromium.org>
Cr-Commit-Position: refs/heads/master@{#766508}
parent b6c58798
...@@ -274,9 +274,17 @@ void SecurityContextInit::InitializeDocumentPolicy( ...@@ -274,9 +274,17 @@ void SecurityContextInit::InitializeDocumentPolicy(
// we have origin trial information available. // we have origin trial information available.
document_policy_ = FilterByOriginTrial(initializer.GetDocumentPolicy(), this); document_policy_ = FilterByOriginTrial(initializer.GetDocumentPolicy(), this);
// Handle Report-Only-Document-Policy HTTP header.
// Console messages generated from logger are discarded, because currently
// there is no way to output them to console.
// Calling |Document::AddConsoleMessage| in
// |SecurityContextInit::ApplyPendingDataToDocument| will have no effect,
// because when the function is called, the document is not fully initialized
// yet (|document_| field in current frame is not yet initialized yet).
PolicyParserMessageBuffer logger("%s", /* discard_message */ true);
base::Optional<DocumentPolicy::ParsedDocumentPolicy> base::Optional<DocumentPolicy::ParsedDocumentPolicy>
report_only_parsed_policy = DocumentPolicyParser::Parse( report_only_parsed_policy = DocumentPolicyParser::Parse(
initializer.ReportOnlyDocumentPolicyHeader()); initializer.ReportOnlyDocumentPolicyHeader(), logger);
if (report_only_parsed_policy) { if (report_only_parsed_policy) {
report_only_document_policy_ = report_only_document_policy_ =
FilterByOriginTrial(*report_only_parsed_policy, this); FilterByOriginTrial(*report_only_parsed_policy, this);
......
...@@ -13,6 +13,8 @@ ...@@ -13,6 +13,8 @@
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
static blink::BlinkFuzzerTestSupport test_support = static blink::BlinkFuzzerTestSupport test_support =
blink::BlinkFuzzerTestSupport(); blink::BlinkFuzzerTestSupport();
blink::DocumentPolicyParser::Parse(WTF::String(data, size));
blink::PolicyParserMessageBuffer logger;
blink::DocumentPolicyParser::Parse(WTF::String(data, size), logger);
return 0; return 0;
} }
...@@ -5,18 +5,50 @@ ...@@ -5,18 +5,50 @@
#include "third_party/blink/renderer/core/feature_policy/document_policy_parser.h" #include "third_party/blink/renderer/core/feature_policy/document_policy_parser.h"
#include "net/http/structured_headers.h" #include "net/http/structured_headers.h"
#include "third_party/blink/public/mojom/feature_policy/policy_value.mojom-blink-forward.h" #include "third_party/blink/public/mojom/feature_policy/policy_value.mojom-blink.h"
namespace blink { namespace blink {
namespace { namespace {
constexpr const char* kReportTo = "report-to"; constexpr const char* kReportTo = "report-to";
constexpr const char* kNone = "none"; constexpr const char* kNone = "none";
constexpr const char* kWildCard = "*"; constexpr const char* kWildCard = "*";
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";
}
}
base::Optional<PolicyValue> ItemToPolicyValue( base::Optional<PolicyValue> ItemToPolicyValue(
const net::structured_headers::Item& item) { const net::structured_headers::Item& item,
mojom::blink::PolicyValueType type) {
switch (type) {
case mojom::blink::PolicyValueType::kDecDouble:
switch (item.Type()) { switch (item.Type()) {
case net::structured_headers::Item::ItemType::kIntegerType: case net::structured_headers::Item::ItemType::kIntegerType:
return PolicyValue(static_cast<double>(item.GetInteger())); return PolicyValue(static_cast<double>(item.GetInteger()));
...@@ -25,6 +57,9 @@ base::Optional<PolicyValue> ItemToPolicyValue( ...@@ -25,6 +57,9 @@ base::Optional<PolicyValue> ItemToPolicyValue(
default: default:
return base::nullopt; return base::nullopt;
} }
default:
return base::nullopt;
}
} }
base::Optional<std::string> ItemToString( base::Optional<std::string> ItemToString(
...@@ -43,15 +78,22 @@ struct ParsedFeature { ...@@ -43,15 +78,22 @@ struct ParsedFeature {
}; };
base::Optional<ParsedFeature> ParseWildcardFeature( base::Optional<ParsedFeature> ParseWildcardFeature(
const net::structured_headers::ParameterizedMember& directive) { const net::structured_headers::ParameterizedMember& directive,
PolicyParserMessageBuffer& logger) {
base::Optional<std::string> endpoint_group; base::Optional<std::string> endpoint_group;
for (const auto& param : directive.params) { for (const auto& param : directive.params) {
if (param.first == kReportTo) { if (param.first == kReportTo) {
endpoint_group = ItemToString(param.second); endpoint_group = ItemToString(param.second);
if (!endpoint_group) if (!endpoint_group) {
logger.Warn("\"report-to\" parameter should be a token.");
return base::nullopt; return base::nullopt;
} }
} else {
// Unrecognized param.
logger.Warn(String::Format("Unrecognized parameter name %s for %s.",
param.first.c_str(), kWildCard));
}
} }
return base::make_optional<ParsedFeature>( return base::make_optional<ParsedFeature>(
...@@ -62,24 +104,27 @@ base::Optional<ParsedFeature> ParseWildcardFeature( ...@@ -62,24 +104,27 @@ base::Optional<ParsedFeature> ParseWildcardFeature(
base::Optional<ParsedFeature> ParseFeature( base::Optional<ParsedFeature> ParseFeature(
const net::structured_headers::ParameterizedMember& directive, const net::structured_headers::ParameterizedMember& directive,
const DocumentPolicyNameFeatureMap& name_feature_map, const DocumentPolicyNameFeatureMap& name_feature_map,
const DocumentPolicyFeatureInfoMap& feature_info_map) { const DocumentPolicyFeatureInfoMap& feature_info_map,
PolicyParserMessageBuffer& logger) {
ParsedFeature parsed_feature; ParsedFeature parsed_feature;
// Directives must not be inner lists. if (directive.member_is_inner_list) {
if (directive.member_is_inner_list) logger.Warn("Directives must not be inner lists.");
return base::nullopt; return base::nullopt;
}
const net::structured_headers::Item& feature_token = const net::structured_headers::Item& feature_token =
directive.member.front().item; directive.member.front().item;
// The item in directive should be token type. if (!feature_token.is_token()) {
if (!feature_token.is_token()) logger.Warn("The item in directive should be token type.");
return base::nullopt; return base::nullopt;
}
std::string feature_name = feature_token.GetString(); const std::string& feature_name = feature_token.GetString();
if (feature_name == kWildCard) if (feature_name == kWildCard)
return ParseWildcardFeature(directive); return ParseWildcardFeature(directive, logger);
auto feature_iter = name_feature_map.find(feature_name); auto feature_iter = name_feature_map.find(feature_name);
...@@ -91,30 +136,41 @@ base::Optional<ParsedFeature> ParseFeature( ...@@ -91,30 +136,41 @@ base::Optional<ParsedFeature> ParseFeature(
feature_iter = name_feature_map.find(feature_name.substr(3)); feature_iter = name_feature_map.find(feature_name.substr(3));
if (feature_iter != name_feature_map.end()) { if (feature_iter != name_feature_map.end()) {
parsed_feature.feature = feature_iter->second; 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() != if (feature_info_map.at(parsed_feature.feature).default_value.Type() !=
mojom::blink::PolicyValueType::kBool) mojom::blink::PolicyValueType::kBool) {
logger.Warn(String::Format(
"\"no-\" prefix is exclusively for policy with boolean policy "
"value. Feature %s does not support boolean policy value.",
feature_name.c_str()));
return base::nullopt; return base::nullopt;
}
parsed_feature.policy_value = PolicyValue(false); parsed_feature.policy_value = PolicyValue(false);
} else { } else {
return base::nullopt; // Unrecognized feature name. logger.Warn(
String::Format("Unrecognized document policy feature name %s.",
feature_name.c_str()));
return base::nullopt;
} }
} else { } else {
return base::nullopt; // Unrecognized feature name. 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();
// Handle boolean value. // Handle boolean value.
// For document policy that has a boolean policy value, policy value is not // For document policy that has a boolean policy value, policy value is not
// specified as directive param. Instead, the value is expressed using "no-" // 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, // prefix, e.g. for feature X, "X" itself in header should be parsed as true,
// "no-X" should be parsed as false. // "no-X" should be parsed as false.
if (feature_info_map.at(parsed_feature.feature).default_value.Type() == if (expected_policy_value_type == mojom::blink::PolicyValueType::kBool &&
mojom::blink::PolicyValueType::kBool &&
parsed_feature.policy_value.Type() == parsed_feature.policy_value.Type() ==
mojom::blink::PolicyValueType::kNull) mojom::blink::PolicyValueType::kNull)
parsed_feature.policy_value = PolicyValue(true); parsed_feature.policy_value = PolicyValue(true);
const std::string& feature_param_name = const std::string& policy_value_param_name =
feature_info_map.at(parsed_feature.feature).feature_param_name; feature_info_map.at(parsed_feature.feature).feature_param_name;
for (const auto& param : directive.params) { for (const auto& param : directive.params) {
const std::string& param_name = param.first; const std::string& param_name = param.first;
...@@ -124,9 +180,13 @@ base::Optional<ParsedFeature> ParseFeature( ...@@ -124,9 +180,13 @@ base::Optional<ParsedFeature> ParseFeature(
// policy violation. // policy violation.
if (param_name == kReportTo) { if (param_name == kReportTo) {
parsed_feature.endpoint_group = ItemToString(param.second); parsed_feature.endpoint_group = ItemToString(param.second);
if (!parsed_feature.endpoint_group) 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; return base::nullopt;
} else if (param_name == feature_param_name) { }
} else if (param_name == policy_value_param_name) {
// Handle policy value. For all non-boolean policy value types, they // Handle policy value. For all non-boolean policy value types, they
// should be specified as FeatureX;f=xxx, with f representing the // should be specified as FeatureX;f=xxx, with f representing the
// |feature_param_name| and xxx representing policy value. // |feature_param_name| and xxx representing policy value.
...@@ -136,17 +196,34 @@ base::Optional<ParsedFeature> ParseFeature( ...@@ -136,17 +196,34 @@ base::Optional<ParsedFeature> ParseFeature(
mojom::blink::PolicyValueType::kNull); mojom::blink::PolicyValueType::kNull);
base::Optional<PolicyValue> policy_value = base::Optional<PolicyValue> policy_value =
ItemToPolicyValue(param.second); ItemToPolicyValue(param.second, expected_policy_value_type);
if (!policy_value) if (!policy_value) {
logger.Warn(String::Format(
"Parameter %s in feature %s should be %s, but get %s.",
policy_value_param_name.c_str(), feature_name.c_str(),
PolicyValueTypeToString(expected_policy_value_type),
ItemTypeToString(param.second.Type())));
return base::nullopt; return base::nullopt;
}
parsed_feature.policy_value = *policy_value; parsed_feature.policy_value = *policy_value;
} else {
// Unrecognized param.
logger.Warn(
String::Format("Unrecognized parameter name %s for feature %s.",
param_name.c_str(), feature_name.c_str()));
} }
} }
// |parsed_feature.policy_value| should be initialized. // |parsed_feature.policy_value| should be initialized.
if (parsed_feature.policy_value.Type() == if (parsed_feature.policy_value.Type() ==
mojom::blink::PolicyValueType::kNull) mojom::blink::PolicyValueType::kNull) {
logger.Warn(String::Format(
"Policy value parameter missing for feature %s. Expected "
"something like \"%s;%s=...\".",
feature_name.c_str(), feature_name.c_str(),
policy_value_param_name.c_str()));
return base::nullopt; return base::nullopt;
}
return parsed_feature; return parsed_feature;
} }
...@@ -184,13 +261,14 @@ void ApplyDefaultEndpoint(DocumentPolicy::ParsedDocumentPolicy& parsed_policy, ...@@ -184,13 +261,14 @@ void ApplyDefaultEndpoint(DocumentPolicy::ParsedDocumentPolicy& parsed_policy,
// static // static
base::Optional<DocumentPolicy::ParsedDocumentPolicy> base::Optional<DocumentPolicy::ParsedDocumentPolicy>
DocumentPolicyParser::Parse(const String& policy_string) { DocumentPolicyParser::Parse(const String& policy_string,
PolicyParserMessageBuffer& logger) {
if (policy_string.IsEmpty()) if (policy_string.IsEmpty())
return base::make_optional<DocumentPolicy::ParsedDocumentPolicy>({}); return base::make_optional<DocumentPolicy::ParsedDocumentPolicy>({});
return ParseInternal(policy_string, GetDocumentPolicyNameFeatureMap(), return ParseInternal(policy_string, GetDocumentPolicyNameFeatureMap(),
GetDocumentPolicyFeatureInfoMap(), GetDocumentPolicyFeatureInfoMap(),
GetAvailableDocumentPolicyFeatures()); GetAvailableDocumentPolicyFeatures(), logger);
} }
// static // static
...@@ -199,17 +277,22 @@ DocumentPolicyParser::ParseInternal( ...@@ -199,17 +277,22 @@ DocumentPolicyParser::ParseInternal(
const String& policy_string, const String& policy_string,
const DocumentPolicyNameFeatureMap& name_feature_map, const DocumentPolicyNameFeatureMap& name_feature_map,
const DocumentPolicyFeatureInfoMap& feature_info_map, const DocumentPolicyFeatureInfoMap& feature_info_map,
const DocumentPolicyFeatureSet& available_features) { const DocumentPolicyFeatureSet& available_features,
PolicyParserMessageBuffer& logger) {
auto root = net::structured_headers::ParseList(policy_string.Ascii()); auto root = net::structured_headers::ParseList(policy_string.Ascii());
if (!root) if (!root) {
logger.Error(
"Parse of document policy failed because of errors reported by "
"structured header parser.");
return base::nullopt; return base::nullopt;
}
DocumentPolicy::ParsedDocumentPolicy parse_result; DocumentPolicy::ParsedDocumentPolicy parse_result;
std::string default_endpoint = ""; std::string default_endpoint = "";
for (const net::structured_headers::ParameterizedMember& directive : for (const net::structured_headers::ParameterizedMember& directive :
root.value()) { root.value()) {
base::Optional<ParsedFeature> parsed_feature_option = base::Optional<ParsedFeature> parsed_feature_option =
ParseFeature(directive, name_feature_map, feature_info_map); ParseFeature(directive, name_feature_map, feature_info_map, logger);
// If a feature fails parsing, ignore the entry. // If a feature fails parsing, ignore the entry.
if (!parsed_feature_option) if (!parsed_feature_option)
continue; continue;
......
...@@ -9,9 +9,9 @@ ...@@ -9,9 +9,9 @@
#include "third_party/blink/public/common/feature_policy/document_policy_features.h" #include "third_party/blink/public/common/feature_policy/document_policy_features.h"
#include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/feature_policy/policy_helper.h" #include "third_party/blink/renderer/core/feature_policy/policy_helper.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
namespace blink { namespace blink {
class CORE_EXPORT DocumentPolicyParser { class CORE_EXPORT DocumentPolicyParser {
STATIC_ONLY(DocumentPolicyParser); STATIC_ONLY(DocumentPolicyParser);
...@@ -19,14 +19,16 @@ class CORE_EXPORT DocumentPolicyParser { ...@@ -19,14 +19,16 @@ class CORE_EXPORT DocumentPolicyParser {
// Parse document policy header and 'policy' attribute on iframe to // Parse document policy header and 'policy' attribute on iframe to
// DocumentPolicy::FeatureState. // DocumentPolicy::FeatureState.
static base::Optional<DocumentPolicy::ParsedDocumentPolicy> Parse( static base::Optional<DocumentPolicy::ParsedDocumentPolicy> Parse(
const String& policy_string); const String& policy_string,
PolicyParserMessageBuffer&);
// Internal parsing method for testing. // Internal parsing method for testing.
static base::Optional<DocumentPolicy::ParsedDocumentPolicy> ParseInternal( static base::Optional<DocumentPolicy::ParsedDocumentPolicy> ParseInternal(
const String& policy_string, const String& policy_string,
const DocumentPolicyNameFeatureMap& name_feature_map, const DocumentPolicyNameFeatureMap& name_feature_map,
const DocumentPolicyFeatureInfoMap& feature_info_map, const DocumentPolicyFeatureInfoMap& feature_info_map,
const DocumentPolicyFeatureSet& available_features); const DocumentPolicyFeatureSet& available_features,
PolicyParserMessageBuffer&);
}; };
} // namespace blink } // namespace blink
......
...@@ -34,9 +34,11 @@ class DocumentPolicyParserTest : public ::testing::Test { ...@@ -34,9 +34,11 @@ class DocumentPolicyParserTest : public ::testing::Test {
~DocumentPolicyParserTest() override = default; ~DocumentPolicyParserTest() override = default;
base::Optional<DocumentPolicy::ParsedDocumentPolicy> Parse( base::Optional<DocumentPolicy::ParsedDocumentPolicy> Parse(
const String& policy_string) { const String& policy_string,
return DocumentPolicyParser::ParseInternal( PolicyParserMessageBuffer& logger) {
policy_string, name_feature_map, feature_info_map, available_features); return DocumentPolicyParser::ParseInternal(policy_string, name_feature_map,
feature_info_map,
available_features, logger);
} }
base::Optional<std::string> Serialize( base::Optional<std::string> Serialize(
...@@ -48,121 +50,330 @@ class DocumentPolicyParserTest : public ::testing::Test { ...@@ -48,121 +50,330 @@ class DocumentPolicyParserTest : public ::testing::Test {
const DocumentPolicyNameFeatureMap name_feature_map; const DocumentPolicyNameFeatureMap name_feature_map;
const DocumentPolicyFeatureInfoMap feature_info_map; const DocumentPolicyFeatureInfoMap feature_info_map;
DocumentPolicyFeatureSet available_features; DocumentPolicyFeatureSet available_features;
};
const char* const kValidPolicies[] = { protected:
"", // An empty policy. struct ParseTestCase {
" ", // An empty policy. const char* test_name;
const char* input_string;
DocumentPolicy::ParsedDocumentPolicy parsed_policy;
Vector<PolicyParserMessageBuffer::Message> messages;
};
// |kPolicyParseTestCases| is made as a member of
// |DocumentPolicyParserTest| because |PolicyParserMessageBuffer::Message| has
// a member of type WTF::String which cannot be statically allocated.
const std::vector<ParseTestCase> kPolicyParseTestCases = {
//
// Parse valid policy strings.
//
{
"Parse an empty policy string.",
"",
/* parsed_policy */
{
/* feature_state */ {},
/* endpoint_map */ {},
},
/* messages */ {},
},
{
"Parse an empty policy string.",
" ",
/* parsed_policy */
{
/* feature_state */ {},
/* endpoint_map */ {},
},
/* messages */ {},
},
{
"Parse bool feature with value true.",
"f-bool", "f-bool",
/* parsed_policy */
{
/* feature_state */ {{kBoolFeature, PolicyValue(true)}},
/* endpoint_map */ {},
},
/* messages */ {},
},
{
"Parse bool feature with value false.",
"no-f-bool", "no-f-bool",
/* parsed_policy */
{
/* feature_state */ {{kBoolFeature, PolicyValue(false)}},
/* endpoint_map */ {},
},
/* messages */ {},
},
{
"Parse double feature with value 1.0.",
"f-double;value=1.0", "f-double;value=1.0",
/* parsed_policy */
{
/* feature_state */ {{kDoubleFeature, PolicyValue(1.0)}},
/* endpoint_map */ {},
},
/* messages */ {},
},
{
"Parse double feature with value literal 2.",
"f-double;value=2", "f-double;value=2",
"f-double;value=2.0,no-f-bool", /* parsed_policy */
"no-f-bool,f-double;value=2.0", {
"no-f-bool;report-to=default,f-double;value=2.0", /* feature_state */ {{kDoubleFeature, PolicyValue(2.0)}},
"no-f-bool;report-to=default,f-double;value=2.0;report-to=default", /* endpoint_map */ {},
"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", /* messages */ {},
"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", "Parse double feature and bool feature.",
"no-f-bool;report-to=none, f-double;value=2.0, *;report-to=none", "f-double;value=1,no-f-bool",
"f-double;value=2;another_value=4", // excessive param should be /* parsed_policy */
// acceptable. {
}; /* feature_state */ {{kBoolFeature, PolicyValue(false)},
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)}}, {kDoubleFeature, PolicyValue(1.0)}},
"f-bool, f-double;value=1.0"}}; /* endpoint_map */ {},
},
const std::pair<const char*, DocumentPolicy::ParsedDocumentPolicy> /* messages */ {},
kPolicyParseTestCases[] = { },
{"no-f-bool,f-double;value=1", {
{{{kBoolFeature, PolicyValue(false)}, "Parse bool feature and double feature.",
"no-f-bool,f-double;value=1",
/* parsed_policy */
{
/* feature_state */ {{kBoolFeature, PolicyValue(false)},
{kDoubleFeature, PolicyValue(1.0)}}, {kDoubleFeature, PolicyValue(1.0)}},
{} /* endpoint_map */}}, /* endpoint_map */ {},
// White-space is allowed in some positions in structured-header. },
{"no-f-bool, f-double;value=1", /* messages */ {},
{{{kBoolFeature, PolicyValue(false)}, },
{
"White-space is allowed in some positions in structured-header.",
"no-f-bool, f-double;value=1",
/* parsed_policy */
{/* feature_state */ {{kBoolFeature, PolicyValue(false)},
{kDoubleFeature, PolicyValue(1.0)}}, {kDoubleFeature, PolicyValue(1.0)}},
{} /* endpoint_map */}}, /* endpoint_map */ {}},
// Unrecognized params are ignored for forwards compatibility. /* messages */ {},
{"no-f-bool,f-double;value=1;unknown_param=xxx", },
{{{kBoolFeature, PolicyValue(false)}, {
"Unrecognized parameters are ignored, but the feature entry should "
"remain valid.",
"no-f-bool,f-double;value=1;unknown_param=xxx",
/* parsed_policy */
{/* feature_state */ {{kBoolFeature, PolicyValue(false)},
{kDoubleFeature, PolicyValue(1.0)}}, {kDoubleFeature, PolicyValue(1.0)}},
{} /* endpoint_map */}}, /* endpoint_map */ {}},
{"no-f-bool,f-double;value=1;report-to=default", /* messages */
{{{kBoolFeature, PolicyValue(false)}, {{mojom::blink::ConsoleMessageLevel::kWarning,
"Unrecognized parameter name unknown_param for feature f-double."}},
},
{
"Parse policy with report endpoint specified.",
"no-f-bool,f-double;value=1;report-to=default",
/* parsed_policy */
{/* feature_state */ {{kBoolFeature, PolicyValue(false)},
{kDoubleFeature, PolicyValue(1.0)}}, {kDoubleFeature, PolicyValue(1.0)}},
{{kDoubleFeature, "default"}}}}, /* endpoint_map */ {{kDoubleFeature, "default"}}},
{"no-f-bool;report-to=default,f-double;value=1", /* messages */ {},
{{{kBoolFeature, PolicyValue(false)}, },
{
"Parse policy with report endpoint specified.",
"no-f-bool;report-to=default,f-double;value=1",
/* parsed_policy */
{/* feature_state */ {{kBoolFeature, PolicyValue(false)},
{kDoubleFeature, PolicyValue(1.0)}}, {kDoubleFeature, PolicyValue(1.0)}},
{{kBoolFeature, "default"}}}}, /* endpoint_map */ {{kBoolFeature, "default"}}},
{"no-f-bool;report-to=none, f-double;value=2.0, *;report-to=default", /* messages */ {},
},
{
"Parse policy with default report endpoint specified. 'none' "
"keyword should overwrite default value assignment.",
"no-f-bool;report-to=none, f-double;value=2.0, *;report-to=default",
/* parsed_policy */
{/* feature_state */ {{kBoolFeature, PolicyValue(false)}, {/* feature_state */ {{kBoolFeature, PolicyValue(false)},
{kDoubleFeature, PolicyValue(2.0)}}, {kDoubleFeature, PolicyValue(2.0)}},
/* endpoint_map */ {{kDoubleFeature, "default"}}}}, /* endpoint_map */ {{kDoubleFeature, "default"}}},
{"no-f-bool;report-to=not_none, f-double;value=2.0, " /* messages */ {},
},
{
"Parse policy with default report endpoint specified.",
"no-f-bool;report-to=not_none, f-double;value=2.0, "
"*;report-to=default", "*;report-to=default",
/* parsed_policy */
{/* feature_state */ {{kBoolFeature, PolicyValue(false)}, {/* feature_state */ {{kBoolFeature, PolicyValue(false)},
{kDoubleFeature, PolicyValue(2.0)}}, {kDoubleFeature, PolicyValue(2.0)}},
/* endpoint_map */ {{kBoolFeature, "not_none"}, /* endpoint_map */ {{kBoolFeature, "not_none"},
{kDoubleFeature, "default"}}}}, {kDoubleFeature, "default"}}},
{"no-f-bool;report-to=not_none, f-double;value=2.0, *;report-to=none", /* messages */ {},
},
{
"Parse policy with default report endpoint 'none'.",
"no-f-bool;report-to=not_none, f-double;value=2.0, *;report-to=none",
/* parsed_policy */
{/* feature_state */ {{kBoolFeature, PolicyValue(false)}, {/* feature_state */ {{kBoolFeature, PolicyValue(false)},
{kDoubleFeature, PolicyValue(2.0)}}, {kDoubleFeature, PolicyValue(2.0)}},
/* endpoint_map */ {{kBoolFeature, "not_none"}}}}, /* endpoint_map */ {{kBoolFeature, "not_none"}}},
// Default endpoint can be specified anywhere in the header, not /* messages */ {},
// necessary at the end. },
{"no-f-bool;report-to=not_none, *;report-to=default, " {
"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", "f-double;value=2.0",
/* parsed_policy */
{/* feature_state */ {{kBoolFeature, PolicyValue(false)}, {/* feature_state */ {{kBoolFeature, PolicyValue(false)},
{kDoubleFeature, PolicyValue(2.0)}}, {kDoubleFeature, PolicyValue(2.0)}},
/* endpoint_map */ {{kBoolFeature, "not_none"}, /* endpoint_map */ {{kBoolFeature, "not_none"},
{kDoubleFeature, "default"}}}}, {kDoubleFeature, "default"}}},
// Default endpoint can be specified multiple times in the header. /* messages */ {},
// According to SH rules, last value wins. },
{"no-f-bool;report-to=not_none, f-double;value=2.0, " {
"*;report-to=default, " "Default endpoint can be specified multiple times in the header. "
"*;report-to=none", "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",
/* parsed_policy */
{/* feature_state */ {{kBoolFeature, PolicyValue(false)}, {/* feature_state */ {{kBoolFeature, PolicyValue(false)},
{kDoubleFeature, PolicyValue(2.0)}}, {kDoubleFeature, PolicyValue(2.0)}},
/* endpoint_map */ {{kBoolFeature, "not_none"}}}}, /* endpoint_map */ {{kBoolFeature, "not_none"}}},
// Even if default endpoint is not specified, none still should be /* messages */ {},
// treated as a reserved keyword for endpoint names. },
{"no-f-bool;report-to=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",
/* parsed_policy */
{/* feature_state */ {{kBoolFeature, PolicyValue(false)}}, {/* feature_state */ {{kBoolFeature, PolicyValue(false)}},
/* endpoint_map */ {}}}, /* endpoint_map */ {}},
/* messages */ {},
},
//
// Parse invalid policies.
//
{
"Parse policy with unrecognized feature name.",
"bad-feature-name",
/* parsed_policy */
{
/* feature_state */ {},
/* endpoint_map */ {},
},
/* messages */
{{mojom::blink::ConsoleMessageLevel::kWarning,
"Unrecognized document policy feature name "
"bad-feature-name."}},
},
{
"Parse policy with unrecognized feature name.",
"no-bad-feature-name",
/* parsed_policy */
{
/* feature_state */ {},
/* endpoint_map */ {},
},
/* messages */
{{mojom::blink::ConsoleMessageLevel::kWarning,
"Unrecognized document policy feature name "
"no-bad-feature-name."}},
},
{
"Parse policy with wrong type of param. Expected double type but get "
"boolean type.",
"f-double;value=?0",
/* parsed_policy */
{
/* feature_state */ {},
/* endpoint_map */ {},
},
/* messages */
{{mojom::blink::ConsoleMessageLevel::kWarning,
"Parameter value in feature f-double should be Double, but get "
"Boolean."}},
},
{
"Policy member should be token instead of string.",
"\"f-bool\"",
/* parsed_policy */
{
/* feature_state */ {},
/* endpoint_map */ {},
},
/* messages */
{{mojom::blink::ConsoleMessageLevel::kWarning,
"The item in directive should be token type."}},
},
{
"Feature token should not be empty.",
"();value=2",
/* parsed_policy */
{
/* feature_state */ {},
/* endpoint_map */ {},
},
/* messages */
{{mojom::blink::ConsoleMessageLevel::kWarning,
"Directives must not be inner lists."}},
},
{
"Too many feature tokens.",
"(f-bool f-double);value=2",
/* parsed_policy */
{
/* feature_state */ {},
/* endpoint_map */ {},
},
/* messages */
{{mojom::blink::ConsoleMessageLevel::kWarning,
"Directives must not be inner lists."}},
},
{
"Missing mandatory parameter.",
"f-double;report-to=default",
/* parsed_policy */
{
/* feature_state */ {},
/* endpoint_map */ {},
},
/* messages */
{{mojom::blink::ConsoleMessageLevel::kWarning,
"Policy value parameter missing for feature f-double. Expected "
"something like \"f-double;value=...\"."}},
},
{
"\"report-to\" parameter value type should be token instead of "
"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<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 DocumentPolicy::FeatureState kParsedPolicies[] = { const DocumentPolicy::FeatureState kParsedPolicies[] = {
{}, // An empty policy {}, // An empty policy
{{kBoolFeature, PolicyValue(false)}}, {{kBoolFeature, PolicyValue(false)}},
...@@ -180,28 +391,15 @@ TEST_F(DocumentPolicyParserTest, SerializeAndParse) { ...@@ -180,28 +391,15 @@ TEST_F(DocumentPolicyParserTest, SerializeAndParse) {
for (const auto& policy : kParsedPolicies) { for (const auto& policy : kParsedPolicies) {
const base::Optional<std::string> policy_string = Serialize(policy); const base::Optional<std::string> policy_string = Serialize(policy);
ASSERT_TRUE(policy_string.has_value()); ASSERT_TRUE(policy_string.has_value());
PolicyParserMessageBuffer logger;
const base::Optional<DocumentPolicy::ParsedDocumentPolicy> reparsed_policy = const base::Optional<DocumentPolicy::ParsedDocumentPolicy> reparsed_policy =
Parse(policy_string.value().c_str()); Parse(policy_string.value().c_str(), logger);
ASSERT_TRUE(reparsed_policy.has_value()); ASSERT_TRUE(reparsed_policy.has_value());
EXPECT_EQ(reparsed_policy.value().feature_state, policy); 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) { TEST_F(DocumentPolicyParserTest, SerializeResultShouldMatch) {
for (const auto& test_case : kPolicySerializationTestCases) { for (const auto& test_case : kPolicySerializationTestCases) {
const DocumentPolicy::FeatureState& policy = test_case.first; const DocumentPolicy::FeatureState& policy = test_case.first;
...@@ -215,12 +413,31 @@ TEST_F(DocumentPolicyParserTest, SerializeResultShouldMatch) { ...@@ -215,12 +413,31 @@ TEST_F(DocumentPolicyParserTest, SerializeResultShouldMatch) {
TEST_F(DocumentPolicyParserTest, ParseResultShouldMatch) { TEST_F(DocumentPolicyParserTest, ParseResultShouldMatch) {
for (const auto& test_case : kPolicyParseTestCases) { for (const auto& test_case : kPolicyParseTestCases) {
const char* input = test_case.first; const String& test_name = test_case.test_name;
const DocumentPolicy::ParsedDocumentPolicy& expected = test_case.second;
const auto result = Parse(input); PolicyParserMessageBuffer logger;
const auto result = Parse(test_case.input_string, logger);
// All tesecases should not return base::nullopt because they all comply to
// structured header syntax.
ASSERT_TRUE(result.has_value()); ASSERT_TRUE(result.has_value());
EXPECT_EQ(result.value(), expected);
EXPECT_EQ(result->endpoint_map, test_case.parsed_policy.endpoint_map)
<< test_name << "\n endpoint map should match";
EXPECT_EQ(result->feature_state, test_case.parsed_policy.feature_state)
<< test_name << "\n feature state should match";
EXPECT_EQ(logger.GetMessages().size(), test_case.messages.size())
<< test_name << "\n messages length should match";
for (auto *it_actual = logger.GetMessages().begin(),
*it_expected = test_case.messages.begin();
it_actual != logger.GetMessages().end() &&
it_expected != test_case.messages.end();
it_actual++, it_expected++) {
EXPECT_EQ(it_actual->level, it_expected->level)
<< test_name << "\n message level should match";
EXPECT_EQ(it_actual->content, it_expected->content)
<< test_name << "\n message content should match";
}
} }
} }
......
...@@ -6,14 +6,57 @@ ...@@ -6,14 +6,57 @@
#define THIRD_PARTY_BLINK_RENDERER_CORE_FEATURE_POLICY_POLICY_HELPER_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_FEATURE_POLICY_POLICY_HELPER_H_
#include "third_party/blink/public/common/feature_policy/feature_policy.h" #include "third_party/blink/public/common/feature_policy/feature_policy.h"
#include "third_party/blink/public/mojom/devtools/console_message.mojom-blink.h"
#include "third_party/blink/public/mojom/feature_policy/document_policy_feature.mojom-blink-forward.h" #include "third_party/blink/public/mojom/feature_policy/document_policy_feature.mojom-blink-forward.h"
#include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-blink-forward.h" #include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-blink-forward.h"
#include "third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom-blink-forward.h" #include "third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom-blink-forward.h"
#include "third_party/blink/renderer/platform/wtf/hash_map.h" #include "third_party/blink/renderer/platform/wtf/hash_map.h"
#include "third_party/blink/renderer/platform/wtf/linked_hash_set.h" #include "third_party/blink/renderer/platform/wtf/linked_hash_set.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace blink { namespace blink {
class PolicyParserMessageBuffer {
public:
struct Message {
mojom::blink::ConsoleMessageLevel level;
String content;
Message(mojom::blink::ConsoleMessageLevel level, const String& content)
: level(level), content(content) {}
};
PolicyParserMessageBuffer() = default;
explicit PolicyParserMessageBuffer(const String& prefix,
bool discard_message = false)
: prefix_(prefix), discard_message_(discard_message) {}
~PolicyParserMessageBuffer() = default;
void Warn(const String& message) {
if (!discard_message_) {
message_buffer_.emplace_back(mojom::blink::ConsoleMessageLevel::kWarning,
prefix_ + message);
}
}
void Error(const String& message) {
if (!discard_message_) {
message_buffer_.emplace_back(mojom::blink::ConsoleMessageLevel::kError,
prefix_ + message);
}
}
const Vector<Message>& GetMessages() { return message_buffer_; }
private:
String prefix_;
Vector<Message> message_buffer_;
// If a dummy message buffer is desired, i.e. messages are not needed for
// the caller, this flag can be set to true and the message buffer will
// discard any incoming messages.
bool discard_message_ = false;
};
using FeatureNameMap = HashMap<String, mojom::blink::FeaturePolicyFeature>; using FeatureNameMap = HashMap<String, mojom::blink::FeaturePolicyFeature>;
......
...@@ -308,8 +308,6 @@ void HTMLIFrameElement::ParseAttribute( ...@@ -308,8 +308,6 @@ void HTMLIFrameElement::ParseAttribute(
} }
} }
// TODO(crbug.com/993790): Emit error message 'endpoint cannot be specified
// on iframe attribute.'.
DocumentPolicy::FeatureState HTMLIFrameElement::ConstructRequiredPolicy() DocumentPolicy::FeatureState HTMLIFrameElement::ConstructRequiredPolicy()
const { const {
if (!RuntimeEnabledFeatures::DocumentPolicyEnabled(&GetDocument())) if (!RuntimeEnabledFeatures::DocumentPolicyEnabled(&GetDocument()))
...@@ -321,19 +319,32 @@ DocumentPolicy::FeatureState HTMLIFrameElement::ConstructRequiredPolicy() ...@@ -321,19 +319,32 @@ DocumentPolicy::FeatureState HTMLIFrameElement::ConstructRequiredPolicy()
mojom::blink::WebFeature::kDocumentPolicyIframePolicyAttribute); mojom::blink::WebFeature::kDocumentPolicyIframePolicyAttribute);
} }
DocumentPolicy::FeatureState new_required_policy = PolicyParserMessageBuffer logger;
DocumentPolicyParser::Parse(required_policy_) DocumentPolicy::ParsedDocumentPolicy new_required_policy =
.value_or(DocumentPolicy::ParsedDocumentPolicy{}) DocumentPolicyParser::Parse(required_policy_, logger)
.feature_state; .value_or(DocumentPolicy::ParsedDocumentPolicy{});
for (const auto& policy_entry : new_required_policy) { for (const auto& message : logger.GetMessages()) {
GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kOther, message.level,
message.content));
}
if (!new_required_policy.endpoint_map.empty()) {
GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kOther,
mojom::blink::ConsoleMessageLevel::kWarning,
"Iframe policy attribute cannot specify reporting endpoint."));
}
for (const auto& policy_entry : new_required_policy.feature_state) {
mojom::blink::DocumentPolicyFeature feature = policy_entry.first; mojom::blink::DocumentPolicyFeature feature = policy_entry.first;
if (!GetDocument().DocumentPolicyFeatureObserved(feature)) { if (!GetDocument().DocumentPolicyFeatureObserved(feature)) {
UMA_HISTOGRAM_ENUMERATION( UMA_HISTOGRAM_ENUMERATION(
"Blink.UseCounter.DocumentPolicy.PolicyAttribute", feature); "Blink.UseCounter.DocumentPolicy.PolicyAttribute", feature);
} }
} }
return new_required_policy; return new_required_policy.feature_state;
} }
ParsedFeaturePolicy HTMLIFrameElement::ConstructContainerPolicy( ParsedFeaturePolicy HTMLIFrameElement::ConstructContainerPolicy(
......
...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
#include "third_party/blink/renderer/core/dom/document_init.h" #include "third_party/blink/renderer/core/dom/document_init.h"
#include "third_party/blink/renderer/core/feature_policy/feature_policy_parser.h" #include "third_party/blink/renderer/core/feature_policy/feature_policy_parser.h"
#include "third_party/blink/renderer/core/testing/dummy_page_holder.h" #include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
#include "third_party/blink/renderer/platform/heap/heap.h" #include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h" #include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
...@@ -361,4 +363,26 @@ TEST_F(HTMLIFrameElementTest, ConstructContainerPolicyWithAllowAttributes) { ...@@ -361,4 +363,26 @@ TEST_F(HTMLIFrameElementTest, ConstructContainerPolicyWithAllowAttributes) {
container_policy[2].feature); container_policy[2].feature);
} }
using HTMLIFrameElementSimTest = SimTest;
TEST_F(HTMLIFrameElementSimTest, PolicyAttributeParsingError) {
blink::ScopedDocumentPolicyForTest sdp(true);
SimRequest main_resource("https://example.com", "text/html");
LoadURL("https://example.com");
main_resource.Complete(R"(
<iframe policy="bad-feature-name"></iframe>
)");
// Note: Parsing of policy attribute string, i.e. call to
// HTMLFrameOwnerElement::UpdateRequiredPolicy(), happens twice in above
// situation:
// - HTMLFrameOwnerElement::LoadOrRedirectSubframe()
// - HTMLIFrameElement::ParseAttribute()
EXPECT_EQ(ConsoleMessages().size(), 2u);
for (const auto& message : ConsoleMessages()) {
EXPECT_TRUE(
message.StartsWith("Unrecognized document policy feature name"));
}
}
} // namespace blink } // namespace blink
...@@ -186,21 +186,6 @@ DocumentLoader::DocumentLoader( ...@@ -186,21 +186,6 @@ DocumentLoader::DocumentLoader(
document_policy_ = CreateDocumentPolicy(); document_policy_ = CreateDocumentPolicy();
// If the document is blocked by document policy, there won't be content
// in the sub-frametree, thus no need to initialize required_policy for
// subtree.
if (!was_blocked_by_document_policy_) {
// Require-Document-Policy header only affects subtree of current document,
// but not the current document.
const DocumentPolicy::FeatureState header_required_policy =
DocumentPolicyParser::Parse(
response_.HttpHeaderField(http_names::kRequireDocumentPolicy))
.value_or(DocumentPolicy::ParsedDocumentPolicy{})
.feature_state;
frame_->SetRequiredDocumentPolicy(DocumentPolicy::MergeFeatureState(
frame_policy_.required_document_policy, header_required_policy));
}
WebNavigationTimings& timings = params_->navigation_timings; WebNavigationTimings& timings = params_->navigation_timings;
if (!timings.input_start.is_null()) if (!timings.input_start.is_null())
document_load_timing_.SetInputStart(timings.input_start); document_load_timing_.SetInputStart(timings.input_start);
...@@ -841,22 +826,53 @@ DocumentPolicy::ParsedDocumentPolicy DocumentLoader::CreateDocumentPolicy() { ...@@ -841,22 +826,53 @@ DocumentPolicy::ParsedDocumentPolicy DocumentLoader::CreateDocumentPolicy() {
url_.ProtocolIs("blob") || url_.ProtocolIs("filesystem")) url_.ProtocolIs("blob") || url_.ProtocolIs("filesystem"))
return {frame_policy_.required_document_policy, {} /* endpoint_map */}; return {frame_policy_.required_document_policy, {} /* endpoint_map */};
// Assume Document policy feature is enabled so we can check the PolicyParserMessageBuffer header_logger("Document-Policy HTTP header: ");
// Required- headers. Will re-validate when we install the new Document. PolicyParserMessageBuffer require_header_logger(
const auto parsed_policy = "Require-Document-Policy HTTP header: ");
// Filtering out features that are disabled by origin trial is done
// in SecurityContextInit when origin trial context is available.
auto parsed_policy =
DocumentPolicyParser::Parse( DocumentPolicyParser::Parse(
response_.HttpHeaderField(http_names::kDocumentPolicy)) response_.HttpHeaderField(http_names::kDocumentPolicy), header_logger)
.value_or(DocumentPolicy::ParsedDocumentPolicy{}); .value_or(DocumentPolicy::ParsedDocumentPolicy{});
// |parsed_policy| can have policies that are disabled by origin trial,
// but |frame_policy_.required_document_policy| cannot.
// It is safe to call |IsPolicyCompatible| as long as required policy is
// checked against origin trial.
if (!DocumentPolicy::IsPolicyCompatible( if (!DocumentPolicy::IsPolicyCompatible(
frame_policy_.required_document_policy, frame_policy_.required_document_policy,
parsed_policy.feature_state)) { parsed_policy.feature_state)) {
was_blocked_by_document_policy_ = true; was_blocked_by_document_policy_ = true;
// When header policy is less strict than required policy, use required // When header policy is less strict than required policy, use required
// policy to initialize document policy for the document. // policy to initialize document policy for the document.
return {frame_policy_.required_document_policy, {} /* endpoint_map */}; parsed_policy = {frame_policy_.required_document_policy,
{} /* endpoint_map */};
}
// Initialize required document policy for subtree.
//
// If the document is blocked by document policy, there won't be content
// in the sub-frametree, thus no need to initialize required_policy for
// subtree.
if (!was_blocked_by_document_policy_) {
// Require-Document-Policy header only affects subtree of current document,
// but not the current document.
const DocumentPolicy::FeatureState header_required_policy =
DocumentPolicyParser::Parse(
response_.HttpHeaderField(http_names::kRequireDocumentPolicy),
require_header_logger)
.value_or(DocumentPolicy::ParsedDocumentPolicy{})
.feature_state;
frame_->SetRequiredDocumentPolicy(DocumentPolicy::MergeFeatureState(
frame_policy_.required_document_policy, header_required_policy));
} }
document_policy_parsing_messages_.AppendVector(header_logger.GetMessages());
document_policy_parsing_messages_.AppendVector(
require_header_logger.GetMessages());
return parsed_policy; return parsed_policy;
} }
...@@ -1413,6 +1429,13 @@ void DocumentLoader::DidInstallNewDocument(Document* document) { ...@@ -1413,6 +1429,13 @@ void DocumentLoader::DidInstallNewDocument(Document* document) {
// header 'Require-Document-Policy'. // header 'Require-Document-Policy'.
if (!frame_policy_.required_document_policy.empty()) if (!frame_policy_.required_document_policy.empty())
UseCounter::Count(*document, WebFeature::kRequiredDocumentPolicy); UseCounter::Count(*document, WebFeature::kRequiredDocumentPolicy);
for (const auto& message : document_policy_parsing_messages_) {
document->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kOther, message.level,
message.content));
}
document_policy_parsing_messages_.clear();
} }
void DocumentLoader::WillCommitNavigation() { void DocumentLoader::WillCommitNavigation() {
......
...@@ -50,6 +50,7 @@ ...@@ -50,6 +50,7 @@
#include "third_party/blink/renderer/bindings/core/v8/source_location.h" #include "third_party/blink/renderer/bindings/core/v8/source_location.h"
#include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/dom/weak_identifier_map.h" #include "third_party/blink/renderer/core/dom/weak_identifier_map.h"
#include "third_party/blink/renderer/core/feature_policy/policy_helper.h"
#include "third_party/blink/renderer/core/frame/dactyloscoper.h" #include "third_party/blink/renderer/core/frame/dactyloscoper.h"
#include "third_party/blink/renderer/core/frame/frame_types.h" #include "third_party/blink/renderer/core/frame/frame_types.h"
#include "third_party/blink/renderer/core/frame/use_counter_helper.h" #include "third_party/blink/renderer/core/frame/use_counter_helper.h"
...@@ -70,7 +71,6 @@ ...@@ -70,7 +71,6 @@
#include "third_party/blink/renderer/platform/weborigin/referrer.h" #include "third_party/blink/renderer/platform/weborigin/referrer.h"
#include "third_party/blink/renderer/platform/wtf/hash_set.h" #include "third_party/blink/renderer/platform/wtf/hash_set.h"
#include "third_party/blink/renderer/platform/wtf/shared_buffer.h" #include "third_party/blink/renderer/platform/wtf/shared_buffer.h"
namespace base { namespace base {
class TickClock; class TickClock;
} }
...@@ -496,6 +496,7 @@ class CORE_EXPORT DocumentLoader : public GarbageCollected<DocumentLoader>, ...@@ -496,6 +496,7 @@ class CORE_EXPORT DocumentLoader : public GarbageCollected<DocumentLoader>,
DocumentPolicy::ParsedDocumentPolicy document_policy_; DocumentPolicy::ParsedDocumentPolicy document_policy_;
bool was_blocked_by_document_policy_; bool was_blocked_by_document_policy_;
Vector<PolicyParserMessageBuffer::Message> document_policy_parsing_messages_;
const Member<ContentSecurityPolicy> content_security_policy_; const Member<ContentSecurityPolicy> content_security_policy_;
const bool was_blocked_by_csp_; const bool was_blocked_by_csp_;
......
...@@ -317,6 +317,33 @@ TEST_F(DocumentLoaderSimTest, DocumentPolicyNoEffectWhenFlagNotSet) { ...@@ -317,6 +317,33 @@ TEST_F(DocumentLoaderSimTest, DocumentPolicyNoEffectWhenFlagNotSet) {
PolicyValue(1.0))); PolicyValue(1.0)));
} }
TEST_F(DocumentLoaderSimTest, ReportDocumentPolicyHeaderParsingError) {
blink::ScopedDocumentPolicyForTest sdp(true);
SimRequest::Params params;
params.response_http_headers = {{"Document-Policy", "bad-feature-name"}};
SimRequest main_resource("https://example.com", "text/html", params);
LoadURL("https://example.com");
main_resource.Finish();
EXPECT_EQ(ConsoleMessages().size(), 1u);
EXPECT_TRUE(
ConsoleMessages().front().StartsWith("Document-Policy HTTP header:"));
}
TEST_F(DocumentLoaderSimTest, ReportRequireDocumentPolicyHeaderParsingError) {
blink::ScopedDocumentPolicyForTest sdp(true);
SimRequest::Params params;
params.response_http_headers = {
{"Require-Document-Policy", "bad-feature-name"}};
SimRequest main_resource("https://example.com", "text/html", params);
LoadURL("https://example.com");
main_resource.Finish();
EXPECT_EQ(ConsoleMessages().size(), 1u);
EXPECT_TRUE(ConsoleMessages().front().StartsWith(
"Require-Document-Policy HTTP header:"));
}
TEST_F(DocumentLoaderSimTest, ReportErrorWhenDocumentPolicyIncompatible) { TEST_F(DocumentLoaderSimTest, ReportErrorWhenDocumentPolicyIncompatible) {
blink::ScopedDocumentPolicyForTest sdp(true); blink::ScopedDocumentPolicyForTest sdp(true);
SimRequest::Params params; SimRequest::Params params;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment