Skip to content

Commit dc1fd32

Browse files
TimvdLippejdm
authored andcommitted
Fix post-request check
A couple of issues: 1. It was missing the integrity metadata check 2. The host part matching was not following the spec with respect to matching `*`
1 parent 58a09ee commit dc1fd32

1 file changed

Lines changed: 60 additions & 19 deletions

File tree

src/lib.rs

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,44 +1404,74 @@ fn get_the_effective_directive_for_inline_checks(type_: InlineCheckType) -> &'st
14041404
/// <https://www.w3.org/TR/CSP/#script-pre-request>
14051405
fn script_directives_prerequest_check(request: &Request, directive: &Directive) -> CheckResult {
14061406
use CheckResult::*;
1407+
// Step 1. If request’s destination is script-like:
14071408
if request_is_script_like(request) {
14081409
let source_list = SourceList(&directive.value[..]);
1410+
// Step 1.1. If the result of executing § 6.7.2.3 Does nonce match source list? on
1411+
// request’s cryptographic nonce metadata and this directive’s value is "Matches", return "Allowed".
14091412
if source_list.does_nonce_match_source_list(&request.nonce) == Matches {
14101413
return Allowed;
14111414
}
1415+
// Step 1.2. If the result of executing § 6.7.2.4 Does integrity metadata match source list? on
1416+
// request’s integrity metadata and this directive’s value is "Matches", return "Allowed".
14121417
if source_list.does_integrity_metadata_match_source_list(&request.integrity_metadata) == Matches {
14131418
return Allowed;
14141419
}
1420+
// Step 1.3. If directive’s value contains a source expression that is an
1421+
// ASCII case-insensitive match for the "'strict-dynamic'" keyword-source:
14151422
if directive.value.iter().any(|ex| ascii_case_insensitive_match(ex, "'strict-dynamic'")) {
1423+
// Step 1.3.1. If the request’s parser metadata is "parser-inserted", return "Blocked".
14161424
if request.parser_metadata == ParserMetadata::ParserInserted {
14171425
return Blocked;
1418-
} else {
1419-
return Allowed;
14201426
}
1427+
// Otherwise, return "Allowed".
1428+
return Allowed;
14211429
}
14221430

1431+
// Step 1.4. If the result of executing § 6.7.2.5 Does request match source list? on
1432+
// request, directive’s value, and policy, is "Does Not Match", return "Blocked".
14231433
if source_list.does_request_match_source_list(request) == DoesNotMatch {
14241434
return Blocked;
14251435
}
14261436
}
1437+
// Step 2. Return "Allowed".
14271438
Allowed
14281439
}
14291440

14301441
/// https://www.w3.org/TR/CSP/#script-post-request
14311442
fn script_directives_postrequest_check(request: &Request, response: &Response, directive: &Directive) -> CheckResult {
14321443
use CheckResult::*;
1444+
// Step 1. If request’s destination is script-like:
14331445
if request_is_script_like(request) {
1446+
// Step 1.1. Call potentially report hash with response, request, directive and policy.
1447+
// TODO
14341448
let source_list = SourceList(&directive.value[..]);
1449+
// Step 1.2. If the result of executing § 6.7.2.3 Does nonce match source list? on
1450+
// request’s cryptographic nonce metadata and this directive’s value is "Matches", return "Allowed".
14351451
if source_list.does_nonce_match_source_list(&request.nonce) == Matches {
14361452
return Allowed;
14371453
}
1438-
if directive.value.iter().any(|ex| ascii_case_insensitive_match(ex, "'strict-dynamic'")) && request.parser_metadata != ParserMetadata::ParserInserted {
1454+
// Step 1.3. If the result of executing § 6.7.2.4 Does integrity metadata match source list? on
1455+
// request’s integrity metadata and this directive’s value is "Matches", return "Allowed".
1456+
if source_list.does_integrity_metadata_match_source_list(&request.integrity_metadata) == Matches {
1457+
return Allowed;
1458+
}
1459+
// Step 1.4. If directive’s value contains "'strict-dynamic'":
1460+
if directive.value.iter().any(|ex| ascii_case_insensitive_match(ex, "'strict-dynamic'")) {
1461+
// Step 1.4.1. If the request’s parser metadata is "parser-inserted", return "Blocked".
1462+
if request.parser_metadata == ParserMetadata::ParserInserted {
1463+
return Blocked;
1464+
}
1465+
// Otherwise, return "Allowed".
14391466
return Allowed;
14401467
}
1468+
// Step 1.5. If the result of executing § 6.7.2.6 Does response to request match source list? on
1469+
// response, request, directive’s value, and policy, is "Does Not Match", return "Blocked".
14411470
if source_list.does_response_to_request_match_source_list(request, response) == DoesNotMatch {
14421471
return Blocked;
14431472
}
14441473
}
1474+
// Step 2. Return "Allowed".
14451475
Allowed
14461476
}
14471477

@@ -1820,39 +1850,50 @@ fn does_url_match_expression_in_origin_with_redirect_count(
18201850
}
18211851

18221852
/// https://www.w3.org/TR/CSP/#match-hosts
1823-
fn host_part_match(a: &str, b: &str) -> MatchResult {
1824-
debug_assert!(!a.is_empty());
1825-
if a.is_empty() {
1853+
fn host_part_match(pattern: &str, host: &str) -> MatchResult {
1854+
debug_assert!(!host.is_empty());
1855+
// Step 1. If host is not a domain, return "Does Not Match".
1856+
if host.is_empty() {
18261857
return DoesNotMatch;
18271858
}
1828-
if a.as_bytes()[0] == b'*' {
1829-
let remaining = &a[1..];
1830-
debug_assert_eq!(&remaining[..1], ".");
1831-
if remaining.len() > b.len() {
1832-
return DoesNotMatch;
1833-
}
1834-
let remaining_b = &b[(b.len()-remaining.len())..];
1835-
debug_assert_eq!(remaining_b.len(), remaining.len());
1836-
if ascii_case_insensitive_match(remaining, remaining_b) {
1859+
if pattern.as_bytes()[0] == b'*' {
1860+
// Step 2. If pattern is "*", return "Matches".
1861+
if pattern.len() == 1 {
18371862
return Matches;
1838-
} else {
1863+
}
1864+
// Step 3. If pattern starts with "*.":
1865+
if pattern.as_bytes()[1] == b'.' {
1866+
// Step 3.1 Let remaining be pattern with the leading U+002A (*) removed and ASCII lowercased.
1867+
let remaining_pattern = &pattern[1..];
1868+
if remaining_pattern.len() > host.len() {
1869+
return DoesNotMatch;
1870+
}
1871+
let remaining_host = &host[(host.len()-remaining_pattern.len())..];
1872+
debug_assert_eq!(remaining_host.len(), remaining_pattern.len());
1873+
// Step 3.2. If host to ASCII lowercase ends with remaining, then return "Matches".
1874+
if ascii_case_insensitive_match(remaining_pattern, remaining_host) {
1875+
return Matches;
1876+
}
1877+
// Step 3.3 Return "Does Not Match".
18391878
return DoesNotMatch;
18401879
}
18411880
}
1842-
if !ascii_case_insensitive_match(a, b) {
1881+
// Step 4. If pattern is not an ASCII case-insensitive match for host, return "Does Not Match".
1882+
if !ascii_case_insensitive_match(pattern, host) {
18431883
return DoesNotMatch;
18441884
}
18451885
static IPV4_ADDRESS_RULE: Lazy<Regex> =
18461886
Lazy::new(|| Regex::new(r#"([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])"#).unwrap());
1847-
if IPV4_ADDRESS_RULE.is_match(a) && a != "127.0.0.1" {
1887+
if IPV4_ADDRESS_RULE.is_match(pattern) && pattern != "127.0.0.1" {
18481888
return DoesNotMatch;
18491889
}
18501890
// The spec uses the phrase "if A is an IPv6 address", without giving specific instructions on
18511891
// how to tell if this is the case. In URLs, IPv6 addresses start with `[`, so let's go with that.
18521892
// See https://url.spec.whatwg.org/#host-parsing
1853-
if a.as_bytes()[0] == b'[' {
1893+
if pattern.as_bytes()[0] == b'[' {
18541894
return DoesNotMatch;
18551895
}
1896+
// Step 5. Return "Matches".
18561897
Matches
18571898
}
18581899

0 commit comments

Comments
 (0)