use std::borrow::Cow;
use memchr::memmem;
use crate::finders::{CRLF, CRLFCRLF};
use crate::sip::content_type::{canonical_body_header, extract_boundary, normalize_media_type};
use crate::sip::{parse_headers, HasHeaders};
use crate::types::{Headers, MimePart, ParsedSipMessage};
#[cfg(test)]
mod tests;
pub(crate) fn is_multipart_type(content_type: Option<&str>) -> bool {
content_type
.map(|ct| normalize_media_type(ct).starts_with("multipart/"))
.unwrap_or(false)
}
pub(crate) fn split_multipart(content_type: Option<&str>, body: &[u8]) -> Option<Vec<MimePart>> {
let boundary = extract_boundary(content_type?)?;
let parts = parse_multipart_body(body, boundary);
(!parts.is_empty()).then_some(parts)
}
impl MimePart {
pub fn media_type(&self) -> Option<Cow<'_, str>> {
HasHeaders::media_type(self)
}
pub fn is_multipart(&self) -> bool {
HasHeaders::is_multipart(self)
}
pub fn multipart_boundary(&self) -> Option<&str> {
HasHeaders::multipart_boundary(self)
}
pub fn body_parts(&self) -> Option<Vec<MimePart>> {
HasHeaders::body_parts(self)
}
}
impl ParsedSipMessage {
pub fn media_type(&self) -> Option<Cow<'_, str>> {
HasHeaders::media_type(self)
}
pub fn is_multipart(&self) -> bool {
HasHeaders::is_multipart(self)
}
pub fn multipart_boundary(&self) -> Option<&str> {
HasHeaders::multipart_boundary(self)
}
pub fn body_parts(&self) -> Option<Vec<MimePart>> {
HasHeaders::body_parts(self)
}
pub fn body_as_parts(&self) -> Vec<MimePart> {
if self.body.is_empty() {
return Vec::new();
}
if let Some(parts) = self.body_parts() {
return parts;
}
vec![self.synthetic_part()]
}
fn synthetic_part(&self) -> MimePart {
let mut headers: Vec<(String, String)> = Vec::new();
if let Some(ct) = self.content_type() {
headers.push(("Content-Type".to_string(), ct.to_string()));
}
for (name, value) in &self.headers {
let Some(canonical) = canonical_body_header(name) else {
continue;
};
if headers
.iter()
.any(|(k, _)| k.eq_ignore_ascii_case(canonical))
{
continue;
}
headers.push((canonical.to_string(), value.clone()));
}
MimePart {
headers: Headers::from(headers),
body: self.body.clone(),
}
}
}
enum BoundaryTail {
Open(usize),
Close,
End,
}
fn boundary_tail(rest: &[u8]) -> Option<BoundaryTail> {
if rest.starts_with(b"--") {
return Some(BoundaryTail::Close);
}
let pad = rest
.iter()
.position(|&b| b != b' ' && b != b'\t')
.unwrap_or(rest.len());
match &rest[pad..] {
[] => Some(BoundaryTail::End),
[b'\r', b'\n', ..] => Some(BoundaryTail::Open(pad + 2)),
_ => None,
}
}
struct BoundaryMatcher {
pattern: Vec<u8>,
finder: memmem::Finder<'static>,
}
impl BoundaryMatcher {
fn new(boundary: &str) -> Self {
let mut pattern = Vec::with_capacity(boundary.len() + 4);
pattern.extend_from_slice(b"\r\n--");
pattern.extend_from_slice(boundary.as_bytes());
let finder = memmem::Finder::new(&pattern).into_owned();
BoundaryMatcher { pattern, finder }
}
fn next_delimiter(&self, body: &[u8], from: usize) -> Option<(usize, usize, BoundaryTail)> {
let dash_boundary = &self.pattern[2..];
if from == 0 && body.starts_with(dash_boundary) {
if let Some(tail) = boundary_tail(&body[dash_boundary.len()..]) {
return Some((0, dash_boundary.len(), tail));
}
}
let mut search = from;
while let Some(rel) = self.finder.find(&body[search..]) {
let crlf = search + rel;
let token_end = crlf + 2 + dash_boundary.len();
if let Some(tail) = boundary_tail(&body[token_end..]) {
return Some((crlf, token_end, tail));
}
search = crlf + 1;
}
None
}
}
fn parse_multipart_body(body: &[u8], boundary: &str) -> Vec<MimePart> {
let matcher = BoundaryMatcher::new(boundary);
let mut parts = Vec::new();
let Some((_, token_end, tail)) = matcher.next_delimiter(body, 0) else {
return parts;
};
let mut cursor = match tail {
BoundaryTail::Open(skip) => token_end + skip,
BoundaryTail::Close | BoundaryTail::End => return parts,
};
loop {
match matcher.next_delimiter(body, cursor) {
Some((part_end, token_end, BoundaryTail::Open(skip))) => {
parts.push(parse_mime_part(&body[cursor..part_end]));
cursor = token_end + skip;
}
Some((part_end, _, BoundaryTail::Close | BoundaryTail::End)) => {
parts.push(parse_mime_part(&body[cursor..part_end]));
break;
}
None => {
parts.push(parse_mime_part(&body[cursor..]));
break;
}
}
}
parts
}
fn parse_mime_part(data: &[u8]) -> MimePart {
match CRLFCRLF.find(data) {
Some(pos) => {
let header_bytes = &data[..pos];
let body = &data[pos + 4..];
let headers = parse_headers(header_bytes);
MimePart {
headers,
body: body.to_vec(),
}
}
None => {
let first_line_end = CRLF.find(data).unwrap_or(data.len());
if memchr::memchr(b':', &data[..first_line_end]).is_some() {
let headers = parse_headers(data);
MimePart {
headers,
body: Vec::new(),
}
} else {
MimePart {
headers: Headers::default(),
body: data.to_vec(),
}
}
}
}
}