const MAX_MULTIPART_BOUNDARY_LEN: usize = 70;
pub(crate) fn media_type(content_type: &str) -> &str {
content_type.split(';').next().map(str::trim).unwrap_or_default()
}
pub(crate) fn has_media_type(content_type: &str, expected: &str) -> bool {
media_type(content_type).eq_ignore_ascii_case(expected)
}
pub(crate) fn is_sse(content_type: &str) -> bool {
has_media_type(content_type, "text/event-stream")
}
pub(crate) fn is_multipart(content_type: &str) -> bool {
media_type(content_type).to_ascii_lowercase().starts_with("multipart/")
}
pub(crate) fn is_valid_multipart_boundary(boundary: &str) -> bool {
let len = boundary.len();
(1..=MAX_MULTIPART_BOUNDARY_LEN).contains(&len) && boundary.bytes().all(is_valid_multipart_boundary_byte)
}
pub(crate) fn parameter(value: &str, parameter_name: &str) -> Option<String> {
for segment in header_parameter_segments(value)?.into_iter().skip(1) {
let Some((name, raw_value)) = segment.split_once('=') else {
continue;
};
if !name.trim().eq_ignore_ascii_case(parameter_name) {
continue;
}
return decode_header_parameter(raw_value.trim());
}
None
}
pub(crate) fn has_parameter_name(value: &str, parameter_name: &str) -> Option<bool> {
for parameter in header_parameter_segments(value)?.into_iter().skip(1) {
let Some((name, _)) = parameter.split_once('=') else {
if parameter.trim().eq_ignore_ascii_case(parameter_name) {
return None;
}
continue;
};
if name.trim().eq_ignore_ascii_case(parameter_name) {
return Some(true);
}
}
Some(false)
}
fn is_valid_multipart_boundary_byte(byte: u8) -> bool {
matches!(
byte,
b'0'..=b'9'
| b'A'..=b'Z'
| b'a'..=b'z'
| b'!'
| b'#'
| b'$'
| b'%'
| b'&'
| b'\''
| b'*'
| b'+'
| b'-'
| b'.'
| b'^'
| b'_'
| b'`'
| b'|'
| b'~'
)
}
fn header_parameter_segments(value: &str) -> Option<Vec<&str>> {
let mut segments = Vec::new();
let mut start = 0;
let mut in_quote = false;
let mut escaped = false;
for (index, ch) in value.char_indices() {
if escaped {
escaped = false;
continue;
}
if in_quote && ch == '\\' {
escaped = true;
continue;
}
if ch == '"' {
in_quote = !in_quote;
continue;
}
if ch == ';' && !in_quote {
segments.push(value[start..index].trim());
start = index + ch.len_utf8();
}
}
if in_quote || escaped {
return None;
}
segments.push(value[start..].trim());
Some(segments)
}
fn decode_header_parameter(value: &str) -> Option<String> {
if !value.starts_with('"') {
return Some(value.trim().to_string());
}
if !value.ends_with('"') || value.len() < 2 {
return None;
}
let mut result = String::new();
let mut chars = value[1..value.len() - 1].chars();
while let Some(ch) = chars.next() {
let value = if ch == '\\' { chars.next()? } else { ch };
if value == '\r' || value == '\n' {
return None;
}
result.push(value);
}
Some(result)
}