chiark / gitweb /
multipart: fix part splitter
[hippotat.git] / src / multipart.rs
1 // Copyright 2021 Ian Jackson and contributors to Hippotat
2 // SPDX-License-Identifier: GPL-3.0-or-later
3 // There is NO WARRANTY.
4
5 use crate::prelude::*;
6
7 #[derive(Debug)]
8 pub struct Component<'b> {
9   pub name: PartName,
10   pub payload: &'b [u8],
11 }
12
13 #[derive(Debug)]
14 #[derive(Eq,PartialEq,Ord,PartialOrd,Hash)]
15 #[allow(non_camel_case_types)]
16 pub enum PartName { m, d, Other }
17
18 pub type BoundaryFinder = memchr::memmem::Finder<'static>;
19
20 #[throws(AE)]
21 /// Processes the start of a component (or terminating boundary).
22 ///
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;
29   let mut rhs =
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")) };
34
35   let mut part_name = None;
36
37   loop {
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")) }
44
45     match (||{
46       let l = str::from_utf8(l).context("interpret part headers as utf-8")?;
47
48       let (_, disposition) = if let Some(y) =
49         regex_captures!(r#"^Content-Disposition[ \t]*:[ \t]*(.*)$"#i, l) { y }
50         else { return Ok(()) };
51
52       let disposition = disposition.trim_end();
53       if disposition.len() >= 100 { throw!(anyhow!(
54         "Content-Disposition value implausibly long"
55       )) }
56
57       // todo: replace with mailparse?
58       // (not in side, dep on charset not in sid)
59       // also seems to box for all the bits
60
61       // This let's us pretend it's a mime type, so we can use mime::Mime
62       let disposition = format!("dummy/{}", disposition);
63
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"#))?;
68
69       let name = match name.as_ref() {
70         "m" => PartName::m,
71         "d" => PartName::d,
72         _   => PartName::Other,
73       };
74
75       if let Some(_) = mem::replace(&mut part_name, Some(name)) {
76         throw!(anyhow!(r#"multiple "name"s in Content-Disposition(s)"#))
77       }
78       Ok::<_,AE>(())
79     })() {
80       Err(e) => warnings.add(&e)?,
81       Ok(()) => { },
82     };
83   }
84
85   //dbg!(DumpHex(rhs));
86
87   Some(Component { name: part_name.unwrap_or(expected), payload: rhs })
88 }
89
90 pub struct ComponentIterator<'b> {
91   at_boundary: &'b [u8],
92   boundary_finder: BoundaryFinder,
93 }
94
95 #[derive(Error,Debug)]
96 #[error("missing mime multipart boundary")]
97 pub struct MissingBoundary;
98
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);
106
107     //dbg!(DumpHex(part));
108
109     (part, ComponentIterator {
110       at_boundary: &buf[next_boundary..],
111       boundary_finder,
112     })
113   }
114
115   fn payload_trim(payload: &[u8]) -> &[u8] {
116     payload.strip_suffix(b"\r").unwrap_or(payload)
117   }
118
119   #[throws(AE)]
120   pub fn next(&mut self, warnings: &mut Warnings, expected: PartName)
121               -> Option<Component<'b>> {
122     if self.at_boundary.is_empty() { return None }
123
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..],
130                        expected)?
131     } {
132       None => {
133         self.at_boundary = &self.at_boundary[0..0];
134         return None;
135       },
136       Some(c) => c,
137     };
138
139     let next_boundary = self.boundary_finder.find(&comp.payload)
140       .ok_or(MissingBoundary)?;
141
142     self.at_boundary = &comp.payload[next_boundary..];
143     comp.payload = Self::payload_trim(&comp.payload[0..next_boundary]);
144
145     //dbg!(DumpHex(comp.payload));
146     //dbg!(DumpHex(&self.at_boundary[0..5]));
147
148     Some(comp)
149   }
150 }
151
152 pub struct MetadataFieldIterator<'b> {
153   buf: &'b [u8],
154   last: Option<usize>,
155   iter: memchr::Memchr<'b>,
156 }
157
158 impl<'b> MetadataFieldIterator<'b> {
159   pub fn new(buf: &'b [u8]) -> Self { Self {
160     buf,
161     last: Some(0),
162     iter: memchr::Memchr::new(b'\n', buf),
163   } }
164
165   #[throws(AE)]
166   pub fn need_next(&mut self) -> &'b str
167   {
168     self.next().ok_or_else(|| anyhow!("missing"))??
169   }
170
171   #[throws(AE)]
172   pub fn need_parse<T>(&mut self) -> T
173   where T: FromStr,
174         AE: From<T::Err>,
175   {
176     self.parse()?.ok_or_else(|| anyhow!("missing"))?
177   }
178
179   #[throws(AE)]
180   pub fn parse<T>(&mut self) -> Option<T>
181   where T: FromStr,
182         AE: From<T::Err>,
183   {
184     let s = if let Some(r) = self.next() { r? } else { return None };
185     Some(s.parse()?)
186   }
187
188   pub fn remaining_bytes_len(&self) -> usize {
189     if let Some(last) = self.last {
190       self.buf.len() - last
191     } else {
192       0
193     }
194   }
195 }
196                                       
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),
204     };
205     self.last = last;
206     let s = str::from_utf8(s).map(|s| s.trim());
207     Some(s)
208   }
209 }
210 impl<'b> std::iter::FusedIterator for MetadataFieldIterator<'b> { }