1 // Copyright 2021 Ian Jackson and contributors to Hippotat
2 // SPDX-License-Identifier: GPL-3.0-or-later
3 // There is NO WARRANTY.
8 pub struct Component<'b> {
10 pub payload: &'b [u8],
14 #[derive(Eq,PartialEq,Ord,PartialOrd,Hash)]
15 #[allow(non_camel_case_types)]
16 pub enum PartName { m, d, Other }
18 pub type BoundaryFinder = memchr::memmem::Finder<'static>;
21 /// Processes the start of a component (or terminating boundary).
23 /// Returned payload is only the start of the payload; the next
24 /// boundary has not been identified.
25 pub fn process_boundary<'b>(warnings: &mut Warnings,
26 after_leader: &'b [u8], expected: PartName)
27 -> Option<Component<'b>> {
28 let rhs = after_leader;
30 if let Some(rhs) = rhs.strip_prefix(b"\r\n") { rhs }
31 else if let Some(_ ) = rhs.strip_prefix(b"--" ) { return None }
32 else if let Some(rhs) = rhs.strip_prefix(b"\n" ) { rhs }
33 else { throw!(anyhow!("invalid multipart delimiter")) };
35 let mut part_name = None;
38 // RHS points to the start of a header line
39 let nl = memchr::memchr(b'\n', rhs)
40 .ok_or_else(|| anyhow!("part headers truncated"))?;
41 let l = &rhs[0..nl]; rhs = &rhs[nl+1..];
42 if l == b"\r" || l == b"" { break } // end of headers
43 if l.starts_with(b"--") { throw!(anyhow!("boundary in part headers")) }
46 let l = str::from_utf8(l).context("interpret part headers as utf-8")?;
48 let (_, disposition) = if let Some(y) =
49 regex_captures!(r#"^Content-Disposition[ \t]*:[ \t]*(.*)$"#i, l) { y }
50 else { return Ok(()) };
52 let disposition = disposition.trim_end();
53 if disposition.len() >= 100 { throw!(anyhow!(
54 "Content-Disposition value implausibly long"
57 // todo: replace with mailparse?
58 // (not in side, dep on charset not in sid)
59 // also seems to box for all the bits
61 // This let's us pretend it's a mime type, so we can use mime::Mime
62 let disposition = format!("dummy/{}", disposition);
64 let disposition: mime::Mime = disposition.parse()
65 .context("parse Content-Disposition")?;
66 let name = disposition.get_param("name")
67 .ok_or_else(|| anyhow!(r#"find "name" in Content-Disposition"#))?;
69 let name = match name.as_ref() {
75 if let Some(_) = mem::replace(&mut part_name, Some(name)) {
76 throw!(anyhow!(r#"multiple "name"s in Content-Disposition(s)"#))
80 Err(e) => warnings.add(&e)?,
87 Some(Component { name: part_name.unwrap_or(expected), payload: rhs })
90 pub struct ComponentIterator<'b> {
91 at_boundary: &'b [u8],
92 boundary_finder: BoundaryFinder,
95 #[derive(Error,Debug)]
96 #[error("missing mime multipart boundary")]
97 pub struct MissingBoundary;
99 impl<'b> ComponentIterator<'b> {
100 #[throws(MissingBoundary)]
101 pub fn resume_mid_component(buf: &'b [u8], boundary_finder: BoundaryFinder)
102 -> (&'b [u8], Self) {
103 let next_boundary = boundary_finder.find(buf).ok_or(MissingBoundary)?;
104 let part = &buf[0..next_boundary];
105 let part = Self::payload_trim(part);
107 //dbg!(DumpHex(part));
109 (part, ComponentIterator {
110 at_boundary: &buf[next_boundary..],
115 fn payload_trim(payload: &[u8]) -> &[u8] {
116 payload.strip_suffix(b"\r").unwrap_or(payload)
120 pub fn next(&mut self, warnings: &mut Warnings, expected: PartName)
121 -> Option<Component<'b>> {
122 if self.at_boundary.is_empty() { return None }
124 let mut comp = match {
125 //dbg!(DumpHex(self.boundary_finder.needle()));
126 let boundary_len = self.boundary_finder.needle().len();
127 //dbg!(boundary_len);
128 process_boundary(warnings,
129 &self.at_boundary[boundary_len..],
133 self.at_boundary = &self.at_boundary[0..0];
139 let next_boundary = self.boundary_finder.find(&comp.payload)
140 .ok_or(MissingBoundary)?;
142 self.at_boundary = &comp.payload[next_boundary..];
143 comp.payload = Self::payload_trim(&comp.payload[0..next_boundary]);
145 //dbg!(DumpHex(comp.payload));
146 //dbg!(DumpHex(&self.at_boundary[0..5]));
152 pub struct MetadataFieldIterator<'b> {
155 iter: memchr::Memchr<'b>,
158 impl<'b> MetadataFieldIterator<'b> {
159 pub fn new(buf: &'b [u8]) -> Self { Self {
162 iter: memchr::Memchr::new(b'\n', buf),
166 pub fn need_next(&mut self) -> &'b str
168 self.next().ok_or_else(|| anyhow!("missing"))??
172 pub fn need_parse<T>(&mut self) -> T
176 self.parse()?.ok_or_else(|| anyhow!("missing"))?
180 pub fn parse<T>(&mut self) -> Option<T>
184 let s = if let Some(r) = self.next() { r? } else { return None };
188 pub fn remaining_bytes_len(&self) -> usize {
189 if let Some(last) = self.last {
190 self.buf.len() - last
197 impl<'b> Iterator for MetadataFieldIterator<'b> {
198 type Item = Result<&'b str, std::str::Utf8Error>;
199 fn next(&mut self) -> Option<Result<&'b str, std::str::Utf8Error>> {
200 let last = self.last?;
201 let (s, last) = match self.iter.next() {
202 Some(nl) => (&self.buf[last..nl], Some(nl+1)),
203 None => (&self.buf[last..], None),
206 let s = str::from_utf8(s).map(|s| s.trim());
210 impl<'b> std::iter::FusedIterator for MetadataFieldIterator<'b> { }