freeswitch_sofia_trace_parser/sip/
multipart.rs1use std::borrow::Cow;
2
3use memchr::memmem;
4
5use crate::finders::{CRLF, CRLFCRLF};
6use crate::sip::content_type::{canonical_body_header, extract_boundary, normalize_media_type};
7use crate::sip::{parse_headers, HasHeaders};
8use crate::types::{Headers, MimePart, ParsedSipMessage};
9
10#[cfg(test)]
11mod tests;
12
13pub(crate) fn is_multipart_type(content_type: Option<&str>) -> bool {
14 content_type
15 .map(|ct| normalize_media_type(ct).starts_with("multipart/"))
16 .unwrap_or(false)
17}
18
19pub(crate) fn split_multipart(content_type: Option<&str>, body: &[u8]) -> Option<Vec<MimePart>> {
22 let boundary = extract_boundary(content_type?)?;
23 let parts = parse_multipart_body(body, boundary);
24 (!parts.is_empty()).then_some(parts)
25}
26
27impl MimePart {
28 pub fn media_type(&self) -> Option<Cow<'_, str>> {
32 HasHeaders::media_type(self)
33 }
34
35 pub fn is_multipart(&self) -> bool {
37 HasHeaders::is_multipart(self)
38 }
39
40 pub fn multipart_boundary(&self) -> Option<&str> {
42 HasHeaders::multipart_boundary(self)
43 }
44
45 pub fn body_parts(&self) -> Option<Vec<MimePart>> {
53 HasHeaders::body_parts(self)
54 }
55}
56
57impl ParsedSipMessage {
58 pub fn media_type(&self) -> Option<Cow<'_, str>> {
62 HasHeaders::media_type(self)
63 }
64
65 pub fn is_multipart(&self) -> bool {
67 HasHeaders::is_multipart(self)
68 }
69
70 pub fn multipart_boundary(&self) -> Option<&str> {
72 HasHeaders::multipart_boundary(self)
73 }
74
75 pub fn body_parts(&self) -> Option<Vec<MimePart>> {
79 HasHeaders::body_parts(self)
80 }
81
82 pub fn body_as_parts(&self) -> Vec<MimePart> {
100 if self.body.is_empty() {
101 return Vec::new();
102 }
103 if let Some(parts) = self.body_parts() {
104 return parts;
105 }
106 vec![self.synthetic_part()]
107 }
108
109 fn synthetic_part(&self) -> MimePart {
112 let mut headers: Vec<(String, String)> = Vec::new();
113 if let Some(ct) = self.content_type() {
114 headers.push(("Content-Type".to_string(), ct.to_string()));
115 }
116 for (name, value) in &self.headers {
117 let Some(canonical) = canonical_body_header(name) else {
118 continue;
119 };
120 if headers
121 .iter()
122 .any(|(k, _)| k.eq_ignore_ascii_case(canonical))
123 {
124 continue;
125 }
126 headers.push((canonical.to_string(), value.clone()));
127 }
128 MimePart {
129 headers: Headers::from(headers),
130 body: self.body.clone(),
131 }
132 }
133}
134
135enum BoundaryTail {
138 Open(usize),
141 Close,
142 End,
144}
145
146fn boundary_tail(rest: &[u8]) -> Option<BoundaryTail> {
149 if rest.starts_with(b"--") {
150 return Some(BoundaryTail::Close);
151 }
152 let pad = rest
153 .iter()
154 .position(|&b| b != b' ' && b != b'\t')
155 .unwrap_or(rest.len());
156 match &rest[pad..] {
157 [] => Some(BoundaryTail::End),
158 [b'\r', b'\n', ..] => Some(BoundaryTail::Open(pad + 2)),
159 _ => None,
160 }
161}
162
163struct BoundaryMatcher {
165 pattern: Vec<u8>,
166 finder: memmem::Finder<'static>,
167}
168
169impl BoundaryMatcher {
170 fn new(boundary: &str) -> Self {
171 let mut pattern = Vec::with_capacity(boundary.len() + 4);
172 pattern.extend_from_slice(b"\r\n--");
173 pattern.extend_from_slice(boundary.as_bytes());
174 let finder = memmem::Finder::new(&pattern).into_owned();
175 BoundaryMatcher { pattern, finder }
176 }
177
178 fn next_delimiter(&self, body: &[u8], from: usize) -> Option<(usize, usize, BoundaryTail)> {
182 let dash_boundary = &self.pattern[2..];
183 if from == 0 && body.starts_with(dash_boundary) {
184 if let Some(tail) = boundary_tail(&body[dash_boundary.len()..]) {
185 return Some((0, dash_boundary.len(), tail));
186 }
187 }
188 let mut search = from;
189 while let Some(rel) = self.finder.find(&body[search..]) {
190 let crlf = search + rel;
191 let token_end = crlf + 2 + dash_boundary.len();
192 if let Some(tail) = boundary_tail(&body[token_end..]) {
193 return Some((crlf, token_end, tail));
194 }
195 search = crlf + 1;
196 }
197 None
198 }
199}
200
201fn parse_multipart_body(body: &[u8], boundary: &str) -> Vec<MimePart> {
202 let matcher = BoundaryMatcher::new(boundary);
203 let mut parts = Vec::new();
204
205 let Some((_, token_end, tail)) = matcher.next_delimiter(body, 0) else {
206 return parts;
207 };
208 let mut cursor = match tail {
209 BoundaryTail::Open(skip) => token_end + skip,
210 BoundaryTail::Close | BoundaryTail::End => return parts,
213 };
214
215 loop {
216 match matcher.next_delimiter(body, cursor) {
217 Some((part_end, token_end, BoundaryTail::Open(skip))) => {
218 parts.push(parse_mime_part(&body[cursor..part_end]));
219 cursor = token_end + skip;
220 }
221 Some((part_end, _, BoundaryTail::Close | BoundaryTail::End)) => {
222 parts.push(parse_mime_part(&body[cursor..part_end]));
223 break;
224 }
225 None => {
228 parts.push(parse_mime_part(&body[cursor..]));
229 break;
230 }
231 }
232 }
233 parts
234}
235
236fn parse_mime_part(data: &[u8]) -> MimePart {
237 match CRLFCRLF.find(data) {
238 Some(pos) => {
239 let header_bytes = &data[..pos];
240 let body = &data[pos + 4..];
241 let headers = parse_headers(header_bytes);
242 MimePart {
243 headers,
244 body: body.to_vec(),
245 }
246 }
247 None => {
248 let first_line_end = CRLF.find(data).unwrap_or(data.len());
251 if memchr::memchr(b':', &data[..first_line_end]).is_some() {
252 let headers = parse_headers(data);
253 MimePart {
254 headers,
255 body: Vec::new(),
256 }
257 } else {
258 MimePart {
259 headers: Headers::default(),
260 body: data.to_vec(),
261 }
262 }
263 }
264 }
265}