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<Vec<u8>> {
let values = headers.get_all(name).iter().map(|val| val.as_bytes());
if values.size_hint() == (0, Some(0)) {
return None;
}
Some(values.collect::<Vec<&[u8]>>().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<Vec<String>> {
get_value_from_header_list(name, headers).map(get_decode_and_split_header_value)
}
pub fn get_decode_and_split_header_value(value: Vec<u8>) -> Vec<String> {
fn char_is_not_quote_or_comma(c: char) -> bool {
c != '\u{0022}' && c != '\u{002C}'
}
let input = value.into_iter().map(char::from).collect::<String>();
let mut position = input.chars().peekable();
let mut values: Vec<String> = vec![];
let mut temporary_value = String::new();
loop {
temporary_value += &*collect_sequence(&mut position, char_is_not_quote_or_comma);
if let Some(&ch) = position.peek() {
if ch == '\u{0022}' {
temporary_value += &*collect_http_quoted_string(&mut position, false);
if position.peek().is_some() {
continue;
}
}
}
temporary_value = temporary_value.trim_matches(HTTP_TAB_OR_SPACE).to_string();
values.push(temporary_value);
temporary_value = String::new();
let Some(ch) = position.next() else {
return values;
};
assert_eq!(ch, '\u{002C}');
}
}
fn collect_sequence<F>(position: &mut Peekable<Chars>, condition: F) -> String
where
F: Fn(char) -> bool,
{
let mut result = String::new();
while let Some(&ch) = position.peek() {
if !condition(ch) {
break;
}
result.push(ch);
position.next();
}
result
}
fn collect_http_quoted_string(position: &mut Peekable<Chars>, extract_value: bool) -> String {
fn char_is_not_quote_or_backslash(c: char) -> bool {
c != '\u{0022}' && c != '\u{005C}'
}
let mut value = String::new();
let should_be_quote = position.next();
if let Some(ch) = should_be_quote {
assert_eq!(ch, '\u{0022}');
if !extract_value {
value.push(ch)
}
}
loop {
value += &*collect_sequence(position, char_is_not_quote_or_backslash);
let Some(quote_or_backslash) = position.next() else {
break;
};
if !extract_value {
value.push(quote_or_backslash);
}
if quote_or_backslash == '\u{005C}' {
if let Some(ch) = position.next() {
value.push(ch);
} else {
if extract_value {
value.push('\u{005C}');
}
break;
}
} else {
assert_eq!(quote_or_backslash, '\u{0022}');
break;
}
}
value
}
pub fn extract_mime_type_as_dataurl_mime(headers: &HeaderMap) -> Option<DataUrlMime> {
let mut charset = None;
let mut essence = String::new();
let mut mime_type = None;
let headers_values = get_decode_and_split_header_name("content-type", headers)?;
for header_value in headers_values.iter() {
match DataUrlMime::from_str(header_value) {
Err(_) => continue,
Ok(temp_mime) => {
let temp_essence = format!("{}/{}", temp_mime.type_, temp_mime.subtype);
if temp_essence == "*/*" {
continue;
}
mime_type = Some(DataUrlMime {
type_: temp_mime.type_.to_string(),
subtype: temp_mime.subtype.to_string(),
parameters: temp_mime.parameters.clone(),
});
let temp_charset = &temp_mime.get_parameter("charset");
if temp_essence != essence {
charset = temp_charset.map(|c| c.to_string());
essence = temp_essence.to_owned();
} else {
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,
})
}
}
},
}
}
mime_type
}
pub fn extract_mime_type(headers: &HeaderMap) -> Option<Vec<u8>> {
extract_mime_type_as_dataurl_mime(headers).map(|m| format!("{}", m).into_bytes())
}
pub fn extract_mime_type_as_mime(headers: &HeaderMap) -> Option<mime::Mime> {
extract_mime_type_as_dataurl_mime(headers).and_then(|mime: DataUrlMime| {
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()
})
}
pub fn determine_nosniff(headers: &HeaderMap) -> bool {
let values = get_decode_and_split_header_name("x-content-type-options", headers);
values.is_some_and(|values| !values.is_empty() && values[0].eq_ignore_ascii_case("nosniff"))
}