Index: net/http/http_security_headers.cc |
diff --git a/net/http/http_security_headers.cc b/net/http/http_security_headers.cc |
index ed7ceccbb73080a7e5dfa1627083a728c3a044fe..6082d42fee9ba5558eb27c9a415a2a8c3e854048 100644 |
--- a/net/http/http_security_headers.cc |
+++ b/net/http/http_security_headers.cc |
@@ -16,6 +16,8 @@ namespace net { |
namespace { |
+enum MaxAgeParsing { REQUIRE_MAX_AGE, DO_NOT_REQUIRE_MAX_AGE }; |
+ |
static_assert(kMaxHSTSAgeSecs <= kuint32max, "kMaxHSTSAgeSecs too large"); |
// MaxAgeToInt converts a string representation of a "whole number" of |
@@ -118,6 +120,90 @@ bool ParseAndAppendPin(std::string::const_iterator begin, |
return true; |
} |
+bool ParseHPKPHeaderImpl(const std::string& value, |
+ MaxAgeParsing max_age_status, |
+ base::TimeDelta* max_age, |
+ bool* include_subdomains, |
+ HashValueVector* hashes, |
+ GURL* report_uri) { |
+ bool parsed_max_age = false; |
+ bool include_subdomains_candidate = false; |
+ uint32 max_age_candidate = 0; |
+ GURL parsed_report_uri; |
+ HashValueVector pins; |
+ bool require_max_age = max_age_status == REQUIRE_MAX_AGE; |
+ |
+ HttpUtil::NameValuePairsIterator name_value_pairs( |
+ value.begin(), value.end(), ';', |
+ HttpUtil::NameValuePairsIterator::VALUES_OPTIONAL); |
+ |
+ while (name_value_pairs.GetNext()) { |
+ if (base::LowerCaseEqualsASCII( |
+ base::StringPiece(name_value_pairs.name_begin(), |
+ name_value_pairs.name_end()), |
+ "max-age")) { |
+ if (!MaxAgeToInt(name_value_pairs.value_begin(), |
+ name_value_pairs.value_end(), &max_age_candidate)) { |
+ return false; |
+ } |
+ parsed_max_age = true; |
+ } else if (base::LowerCaseEqualsASCII( |
+ base::StringPiece(name_value_pairs.name_begin(), |
+ name_value_pairs.name_end()), |
+ "pin-sha1")) { |
+ // Pins are always quoted. |
+ if (!name_value_pairs.value_is_quoted() || |
+ !ParseAndAppendPin(name_value_pairs.value_begin(), |
+ name_value_pairs.value_end(), HASH_VALUE_SHA1, |
+ &pins)) { |
+ return false; |
+ } |
+ } else if (base::LowerCaseEqualsASCII( |
+ base::StringPiece(name_value_pairs.name_begin(), |
+ name_value_pairs.name_end()), |
+ "pin-sha256")) { |
+ // Pins are always quoted. |
+ if (!name_value_pairs.value_is_quoted() || |
+ !ParseAndAppendPin(name_value_pairs.value_begin(), |
+ name_value_pairs.value_end(), HASH_VALUE_SHA256, |
+ &pins)) { |
+ return false; |
+ } |
+ } else if (base::LowerCaseEqualsASCII( |
+ base::StringPiece(name_value_pairs.name_begin(), |
+ name_value_pairs.name_end()), |
+ "includesubdomains")) { |
+ include_subdomains_candidate = true; |
+ } else if (base::LowerCaseEqualsASCII( |
+ base::StringPiece(name_value_pairs.name_begin(), |
+ name_value_pairs.name_end()), |
+ "report-uri")) { |
+ // report-uris are always quoted. |
+ if (!name_value_pairs.value_is_quoted()) |
+ return false; |
+ |
+ parsed_report_uri = GURL(name_value_pairs.value()); |
+ if (parsed_report_uri.is_empty() || !parsed_report_uri.is_valid()) |
+ return false; |
+ } else { |
+ // Silently ignore unknown directives for forward compatibility. |
+ } |
+ } |
+ |
+ if (!name_value_pairs.valid()) |
+ return false; |
+ |
+ if (!parsed_max_age && require_max_age) |
+ return false; |
+ |
+ *max_age = base::TimeDelta::FromSeconds(max_age_candidate); |
+ *include_subdomains = include_subdomains_candidate; |
+ hashes->swap(pins); |
+ *report_uri = parsed_report_uri; |
+ |
+ return true; |
+} |
+ |
} // namespace |
// Parse the Strict-Transport-Security header, as currently defined in |
@@ -252,7 +338,7 @@ bool ParseHSTSHeader(const std::string& value, |
} |
} |
-// "Public-Key-Pins[-Report-Only]" ":" |
+// "Public-Key-Pins" ":" |
// "max-age" "=" delta-seconds ";" |
// "pin-" algo "=" base64 [ ";" ... ] |
// [ ";" "includeSubdomains" ] |
@@ -263,84 +349,42 @@ bool ParseHPKPHeader(const std::string& value, |
bool* include_subdomains, |
HashValueVector* hashes, |
GURL* report_uri) { |
- bool parsed_max_age = false; |
- bool include_subdomains_candidate = false; |
- uint32 max_age_candidate = 0; |
- GURL parsed_report_uri; |
- HashValueVector pins; |
- |
- HttpUtil::NameValuePairsIterator name_value_pairs( |
- value.begin(), value.end(), ';', |
- HttpUtil::NameValuePairsIterator::VALUES_OPTIONAL); |
- |
- while (name_value_pairs.GetNext()) { |
- if (base::LowerCaseEqualsASCII( |
- base::StringPiece(name_value_pairs.name_begin(), |
- name_value_pairs.name_end()), |
- "max-age")) { |
- if (!MaxAgeToInt(name_value_pairs.value_begin(), |
- name_value_pairs.value_end(), &max_age_candidate)) { |
- return false; |
- } |
- parsed_max_age = true; |
- } else if (base::LowerCaseEqualsASCII( |
- base::StringPiece(name_value_pairs.name_begin(), |
- name_value_pairs.name_end()), |
- "pin-sha1")) { |
- // Pins are always quoted. |
- if (!name_value_pairs.value_is_quoted() || |
- !ParseAndAppendPin(name_value_pairs.value_begin(), |
- name_value_pairs.value_end(), HASH_VALUE_SHA1, |
- &pins)) { |
- return false; |
- } |
- } else if (base::LowerCaseEqualsASCII( |
- base::StringPiece(name_value_pairs.name_begin(), |
- name_value_pairs.name_end()), |
- "pin-sha256")) { |
- // Pins are always quoted. |
- if (!name_value_pairs.value_is_quoted() || |
- !ParseAndAppendPin(name_value_pairs.value_begin(), |
- name_value_pairs.value_end(), HASH_VALUE_SHA256, |
- &pins)) { |
- return false; |
- } |
- } else if (base::LowerCaseEqualsASCII( |
- base::StringPiece(name_value_pairs.name_begin(), |
- name_value_pairs.name_end()), |
- "includesubdomains")) { |
- include_subdomains_candidate = true; |
- } else if (base::LowerCaseEqualsASCII( |
- base::StringPiece(name_value_pairs.name_begin(), |
- name_value_pairs.name_end()), |
- "report-uri")) { |
- // report-uris are always quoted. |
- if (!name_value_pairs.value_is_quoted()) |
- return false; |
- |
- parsed_report_uri = GURL(name_value_pairs.value()); |
- if (parsed_report_uri.is_empty() || !parsed_report_uri.is_valid()) |
- return false; |
- } else { |
- // Silently ignore unknown directives for forward compatibility. |
- } |
- } |
- |
- if (!name_value_pairs.valid()) |
- return false; |
- |
- if (!parsed_max_age) |
+ base::TimeDelta candidate_max_age; |
+ bool candidate_include_subdomains; |
+ HashValueVector candidate_hashes; |
+ GURL candidate_report_uri; |
+ |
+ if (!ParseHPKPHeaderImpl(value, REQUIRE_MAX_AGE, &candidate_max_age, |
+ &candidate_include_subdomains, &candidate_hashes, |
+ &candidate_report_uri)) { |
return false; |
+ } |
- if (!IsPinListValid(pins, chain_hashes)) |
+ if (!IsPinListValid(candidate_hashes, chain_hashes)) |
return false; |
- *max_age = base::TimeDelta::FromSeconds(max_age_candidate); |
- *include_subdomains = include_subdomains_candidate; |
- hashes->swap(pins); |
- *report_uri = parsed_report_uri; |
- |
+ *max_age = candidate_max_age; |
+ *include_subdomains = candidate_include_subdomains; |
+ hashes->swap(candidate_hashes); |
+ *report_uri = candidate_report_uri; |
return true; |
} |
+// "Public-Key-Pins-Report-Only" ":" |
+// [ "max-age" "=" delta-seconds ";" ] |
+// "pin-" algo "=" base64 [ ";" ... ] |
+// [ ";" "includeSubdomains" ] |
+// [ ";" "report-uri" "=" uri-reference ] |
+bool ParseHPKPReportOnlyHeader(const std::string& value, |
+ HashValueVector* hashes, |
+ GURL* report_uri) { |
+ // max-age and includeSubdomains are irrelevant for Report-Only |
+ // headers. |
+ base::TimeDelta unused_max_age; |
+ bool unused_include_subdomains; |
+ |
+ return ParseHPKPHeaderImpl(value, DO_NOT_REQUIRE_MAX_AGE, &unused_max_age, |
+ &unused_include_subdomains, hashes, report_uri); |
+} |
+ |
} // namespace net |