mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Fix some 'nosniff' tests
This commit is contained in:
parent
d14628d2ea
commit
3b3824078d
4 changed files with 182 additions and 15 deletions
173
components/net/fetch/headers.rs
Normal file
173
components/net/fetch/headers.rs
Normal file
|
@ -0,0 +1,173 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use headers::HeaderMap;
|
||||
use std::iter::Peekable;
|
||||
use std::str::Chars;
|
||||
|
||||
/// <https://fetch.spec.whatwg.org/#http-tab-or-space>
|
||||
const HTTP_TAB_OR_SPACE: &[char] = &['\u{0009}', '\u{0020}'];
|
||||
|
||||
/// <https://fetch.spec.whatwg.org/#determine-nosniff>
|
||||
pub fn determine_nosniff(headers: &HeaderMap) -> bool {
|
||||
let values = get_header_value_as_list("x-content-type-options", headers);
|
||||
|
||||
match values {
|
||||
None => false,
|
||||
Some(values) => !values.is_empty() && (&values[0]).eq_ignore_ascii_case("nosniff"),
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://fetch.spec.whatwg.org/#concept-header-list-get-decode-split>
|
||||
fn get_header_value_as_list(name: &str, headers: &HeaderMap) -> Option<Vec<String>> {
|
||||
fn char_is_not_quote_or_comma(c: char) -> bool {
|
||||
return c != '\u{0022}' && c != '\u{002C}';
|
||||
}
|
||||
|
||||
// Step 1
|
||||
let initial_value = get_value_from_header_list(name, headers);
|
||||
|
||||
if let Some(input) = initial_value {
|
||||
// Step 4
|
||||
let mut position = input.chars().peekable();
|
||||
|
||||
// Step 5
|
||||
let mut values: Vec<String> = vec![];
|
||||
|
||||
// Step 6
|
||||
let mut value = String::new();
|
||||
|
||||
// Step 7
|
||||
while position.peek().is_some() {
|
||||
// Step 7.1
|
||||
value += &*collect_sequence(&mut position, char_is_not_quote_or_comma);
|
||||
|
||||
// Step 7.2
|
||||
if let Some(&ch) = position.peek() {
|
||||
if ch == '\u{0022}' {
|
||||
// Step 7.2.1.1
|
||||
value += &*collect_http_quoted_string(&mut position, false);
|
||||
|
||||
// Step 7.2.1.2
|
||||
if position.peek().is_some() {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// ch == '\u{002C}'
|
||||
|
||||
// Step 7.2.2.2
|
||||
position.next();
|
||||
}
|
||||
}
|
||||
|
||||
// Step 7.3
|
||||
value = value.trim_matches(HTTP_TAB_OR_SPACE).to_string();
|
||||
|
||||
// Step 7.4
|
||||
values.push(value);
|
||||
|
||||
// Step 7.5
|
||||
value = String::new();
|
||||
}
|
||||
|
||||
return Some(values);
|
||||
}
|
||||
|
||||
// Step 2
|
||||
return None;
|
||||
}
|
||||
|
||||
/// <https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points>
|
||||
fn collect_sequence<F>(position: &mut Peekable<Chars>, condition: F) -> String
|
||||
where
|
||||
F: Fn(char) -> bool,
|
||||
{
|
||||
// Step 1
|
||||
let mut result = String::new();
|
||||
|
||||
// Step 2
|
||||
while let Some(&ch) = position.peek() {
|
||||
if !condition(ch) {
|
||||
break;
|
||||
}
|
||||
result.push(ch);
|
||||
position.next();
|
||||
}
|
||||
|
||||
// Step 3
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <https://fetch.spec.whatwg.org/#collect-an-http-quoted-string>
|
||||
fn collect_http_quoted_string(position: &mut Peekable<Chars>, extract_value: bool) -> String {
|
||||
fn char_is_not_quote_or_backslash(c: char) -> bool {
|
||||
return c != '\u{0022}' && c != '\u{005C}';
|
||||
}
|
||||
|
||||
// Step 2
|
||||
// We will store the 'extracted value' or the raw value
|
||||
let mut value = String::new();
|
||||
|
||||
// Step 3, 4
|
||||
let should_be_quote = position.next();
|
||||
if let Some(ch) = should_be_quote {
|
||||
if !extract_value {
|
||||
value.push(ch)
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5
|
||||
loop {
|
||||
// Step 5.1
|
||||
value += &*collect_sequence(position, char_is_not_quote_or_backslash);
|
||||
|
||||
// Step 5.2
|
||||
if position.peek().is_none() {
|
||||
break;
|
||||
}
|
||||
|
||||
// Step 5.3, 5.4
|
||||
let quote_or_backslash = position.next().unwrap();
|
||||
if !extract_value {
|
||||
value.push(quote_or_backslash);
|
||||
}
|
||||
|
||||
if quote_or_backslash == '\u{005C}' {
|
||||
if let Some(ch) = position.next() {
|
||||
value.push(ch);
|
||||
} else {
|
||||
// Step 5.5.1
|
||||
if extract_value {
|
||||
value.push('\u{005C}');
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Step 5.6.1
|
||||
// assert quote_or_backslash is a quote
|
||||
|
||||
// Step 5.6.2
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6, 7
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <https://fetch.spec.whatwg.org/#concept-header-list-get>
|
||||
fn get_value_from_header_list(name: &str, headers: &HeaderMap) -> Option<String> {
|
||||
let values = headers
|
||||
.get_all(name)
|
||||
.iter()
|
||||
.map(|val| val.to_str().unwrap());
|
||||
|
||||
// Step 1
|
||||
if values.size_hint() == (0, Some(0)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Step 2
|
||||
return Some(values.collect::<Vec<&str>>().join(", "));
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
use crate::data_loader::decode;
|
||||
use crate::fetch::cors_cache::CorsCache;
|
||||
use crate::fetch::headers::determine_nosniff;
|
||||
use crate::filemanager_thread::{FileManager, FILE_CHUNK_SIZE};
|
||||
use crate::http_loader::{determine_requests_referrer, http_fetch, HttpState};
|
||||
use crate::http_loader::{set_default_accept, set_default_accept_language};
|
||||
|
@ -834,18 +835,12 @@ pub fn should_be_blocked_due_to_nosniff(
|
|||
destination: Destination,
|
||||
response_headers: &HeaderMap,
|
||||
) -> bool {
|
||||
// Steps 1-3.
|
||||
// TODO(eijebong): Replace this once typed headers allow custom ones...
|
||||
if response_headers
|
||||
.get("x-content-type-options")
|
||||
.map_or(true, |val| {
|
||||
val.to_str().unwrap_or("").to_lowercase() != "nosniff"
|
||||
})
|
||||
{
|
||||
// Step 1
|
||||
if !determine_nosniff(response_headers) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 4
|
||||
// Step 2
|
||||
// Note: an invalid MIME type will produce a `None`.
|
||||
let content_type_header = response_headers.typed_get::<ContentType>();
|
||||
|
||||
|
@ -877,19 +872,19 @@ pub fn should_be_blocked_due_to_nosniff(
|
|||
}
|
||||
|
||||
match content_type_header {
|
||||
// Step 6
|
||||
// Step 4
|
||||
Some(ref ct) if destination.is_script_like() => {
|
||||
!is_javascript_mime_type(&ct.clone().into())
|
||||
},
|
||||
|
||||
// Step 7
|
||||
// Step 5
|
||||
Some(ref ct) if destination == Destination::Style => {
|
||||
let m: mime::Mime = ct.clone().into();
|
||||
m.type_() != mime::TEXT && m.subtype() != mime::CSS
|
||||
},
|
||||
|
||||
None if destination == Destination::Style || destination.is_script_like() => true,
|
||||
// Step 8
|
||||
// Step 6
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,9 +33,11 @@ pub mod resource_thread;
|
|||
mod storage_thread;
|
||||
pub mod subresource_integrity;
|
||||
mod websocket_loader;
|
||||
|
||||
/// An implementation of the [Fetch specification](https://fetch.spec.whatwg.org/)
|
||||
pub mod fetch {
|
||||
pub mod cors_cache;
|
||||
pub mod headers;
|
||||
pub mod methods;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,3 @@
|
|||
[X-Content-Type-Options%3A%20nosniff%0C]
|
||||
expected: FAIL
|
||||
|
||||
[X-Content-Type-Options%3A%20nosniff%2C%2C%40%23%24%23%25%25%26%5E%26%5E*()()11!]
|
||||
expected: FAIL
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue