/* 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 std::iter::Peekable; use std::str::{Chars, FromStr}; use data_url::mime::Mime as DataUrlMime; use headers::HeaderMap; /// const HTTP_TAB_OR_SPACE: &[char] = &['\u{0009}', '\u{0020}']; /// pub fn get_value_from_header_list(name: &str, headers: &HeaderMap) -> Option> { let values = headers.get_all(name).iter().map(|val| val.as_bytes()); // Step 1: If list does not contain name, then return null. if values.size_hint() == (0, Some(0)) { return None; } // Step 2: Return the values of all headers in list whose name is a byte-case-insensitive match // for name, separated from each other by 0x2C 0x20, in order. Some(values.collect::>().join(&[0x2C, 0x20][..])) } /// pub fn is_forbidden_method(method: &[u8]) -> bool { matches!( method.to_ascii_lowercase().as_slice(), b"connect" | b"trace" | b"track" ) } /// pub fn get_decode_and_split_header_name(name: &str, headers: &HeaderMap) -> Option> { // Step 1: Let value be the result of getting name from list. // Step 2: If value is null, then return null. // Step 3: Return the result of getting, decoding, and splitting value. get_value_from_header_list(name, headers).map(get_decode_and_split_header_value) } /// pub fn get_decode_and_split_header_value(value: Vec) -> Vec { fn char_is_not_quote_or_comma(c: char) -> bool { c != '\u{0022}' && c != '\u{002C}' } // Step 1: Let input be the result of isomorphic decoding value. let input = value.into_iter().map(char::from).collect::(); // Step 2: Let position be a position variable for input, initially pointing at the start of // input. let mut position = input.chars().peekable(); // Step 3: Let values be a list of strings, initially « ». let mut values: Vec = vec![]; // Step 4: Let temporaryValue be the empty string. let mut temporary_value = String::new(); // Step 5: While true: while position.peek().is_some() { // Step 5.1: Append the result of collecting a sequence of code points that are not U+0022 // (") or U+002C (,) from input, given position, to temporaryValue. temporary_value += &*collect_sequence(&mut position, char_is_not_quote_or_comma); // Step 5.2: If position is not past the end of input and the code point at position within // input is U+0022 ("): if let Some(&ch) = position.peek() { if ch == '\u{0022}' { // Step 5.2.1: Append the result of collecting an HTTP quoted string from input, // given position, to temporaryValue. temporary_value += &*collect_http_quoted_string(&mut position, false); // Step 5.2.2: If position is not past the end of input, then continue. if position.peek().is_some() { continue; } } else { // Step 5.2.2: If position is not past the end of input, then continue. position.next(); } } // Step 5.3: Remove all HTTP tab or space from the start and end of temporaryValue. temporary_value = temporary_value.trim_matches(HTTP_TAB_OR_SPACE).to_string(); // Step 5.4: Append temporaryValue to values. values.push(temporary_value); // Step 5.5: Set temporaryValue to the empty string. temporary_value = String::new(); } values } /// fn collect_sequence(position: &mut Peekable, condition: F) -> String where F: Fn(char) -> bool, { // Step 1: Let result be the empty string. let mut result = String::new(); // Step 2: While position doesn’t point past the end of input and the code point at position // within input meets the condition condition: while let Some(&ch) = position.peek() { if !condition(ch) { break; } // Step 2.1: Append that code point to the end of result. result.push(ch); // Step 2.2: Advance position by 1. position.next(); } // Step 3: Return result. result } /// fn collect_http_quoted_string(position: &mut Peekable, extract_value: bool) -> String { fn char_is_not_quote_or_backslash(c: char) -> bool { c != '\u{0022}' && c != '\u{005C}' } // Step 2: let value be the empty string // 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: While true: loop { // Step 5.1: Append the result of collecting a sequence of code points that are not U+0022 // (") or U+005C (\) from input, given position, to value. value += &*collect_sequence(position, char_is_not_quote_or_backslash); // Step 5.2: If position is past the end of input, then break. if position.peek().is_none() { break; } // Step 5.3: Let quoteOrBackslash be the code point at position within input. // Step 5.4: Advance position by 1. let quote_or_backslash = position.next().unwrap(); if !extract_value { value.push(quote_or_backslash); } // Step 5.5: If quoteOrBackslash is U+005C (\), then: if quote_or_backslash == '\u{005C}' { if let Some(ch) = position.next() { // Step 5.5.2: Append the code point at position within input to value. value.push(ch); } else { // Step 5.5.1: If position is past the end of input, then append U+005C (\) to value and break. if extract_value { value.push('\u{005C}'); } break; } } else { // Step 5.6.1: Assert quote_or_backslash is a quote assert_eq!(quote_or_backslash, '\u{0022}'); // Step 5.6.2: break break; } } // Step 6, 7 value } /// /// This function uses data_url::Mime to parse the MIME Type because /// mime::Mime does not provide a parser following the Fetch spec /// see pub fn extract_mime_type_as_dataurl_mime(headers: &HeaderMap) -> Option { // > 1: Let charset be null. let mut charset = None; // > 2: Let essence be null. let mut essence = String::new(); // > 3: Let mimeType be null. let mut mime_type = None; // > 4: Let values be the result of getting, decoding, and splitting `Content-Type` // from headers. // > 5: If values is null, then return failure. let headers_values = get_decode_and_split_header_name("content-type", headers)?; // > 6: For each value of values: for header_value in headers_values.iter() { // > 6.1: Let temporaryMimeType be the result of parsing value. match DataUrlMime::from_str(header_value) { // > 6.2: If temporaryMimeType is failure or its essence is "*/*", then continue. Err(_) => continue, Ok(temp_mime) => { let temp_essence = format!("{}/{}", temp_mime.type_, temp_mime.subtype); // > 6.2: If temporaryMimeType is failure or its essence is "*/*", then // continue. if temp_essence == "*/*" { continue; } // > 6.3: Set mimeType to temporaryMimeType. mime_type = Some(DataUrlMime { type_: temp_mime.type_.to_string(), subtype: temp_mime.subtype.to_string(), parameters: temp_mime.parameters.clone(), }); // > 6.4: If mimeType’s essence is not essence, then: let temp_charset = &temp_mime.get_parameter("charset"); if temp_essence != essence { // > 6.4.1: Set charset to null. // > 6.4.2: If mimeType’s parameters["charset"] exists, then set // charset to mimeType’s parameters["charset"]. charset = temp_charset.map(|c| c.to_string()); // > 6.4.3: Set essence to mimeType’s essence. essence = temp_essence.to_owned(); } else { // > 6.5: Otherwise, if mimeType’s parameters["charset"] does not exist, // and charset is non-null, set mimeType’s parameters["charset"] to charset. if temp_charset.is_none() && charset.is_some() { let DataUrlMime { type_: t, subtype: st, parameters: p, } = mime_type.unwrap(); let mut params = p; params.push(("charset".to_string(), charset.clone().unwrap())); mime_type = Some(DataUrlMime { type_: t.to_string(), subtype: st.to_string(), parameters: params, }) } } }, } } // > 7: If mimeType is null, then return failure. // > 8: Return mimeType. mime_type } pub fn extract_mime_type(headers: &HeaderMap) -> Option> { extract_mime_type_as_dataurl_mime(headers).map(|m| format!("{}", m).into_bytes()) } pub fn extract_mime_type_as_mime(headers: &HeaderMap) -> Option { extract_mime_type_as_dataurl_mime(headers).and_then(|mime: DataUrlMime| { // Try to transform a data-url::mime::Mime into a mime::Mime let mut mime_as_str = format!("{}/{}", mime.type_, mime.subtype); for p in mime.parameters { mime_as_str.push_str(format!("; {}={}", p.0, p.1).as_str()); } mime_as_str.parse().ok() }) }