chiark / gitweb /
multipart: a todo
[hippotat.git] / src / multipart.rs
index 112712bb8d5f4ad79d78f5e66473039b4357f46f..a181a1f9a47746c88d4fe1f022629e7c20296fc7 100644 (file)
@@ -7,7 +7,7 @@ use crate::prelude::*;
 #[derive(Debug)]
 pub struct Component<'b> {
   pub name: PartName,
-  pub payload_start: &'b [u8],
+  pub payload: &'b [u8],
 }
 
 #[derive(Debug)]
@@ -15,10 +15,16 @@ pub struct Component<'b> {
 #[allow(non_camel_case_types)]
 pub enum PartName { m, d, Other }
 
+pub type BoundaryFinder = memchr::memmem::Finder<'static>;
+
 #[throws(AE)]
-pub fn process_component<'b>(warnings: &mut Warnings,
-                             after_leader: &'b [u8], expected: PartName)
-                             -> Option<Component<'b>> {
+/// Processes the start of a component (or terminating boundary).
+///
+/// Returned payload is only the start of the payload; the next
+/// boundary has not been identified.
+pub fn process_boundary<'b>(warnings: &mut Warnings,
+                            after_leader: &'b [u8], expected: PartName)
+                            -> Option<Component<'b>> {
   let rhs = after_leader;
   let mut rhs =
        if let Some(rhs) = rhs.strip_prefix(b"\r\n") { rhs         }
@@ -47,6 +53,11 @@ pub fn process_component<'b>(warnings: &mut Warnings,
       if disposition.len() >= 100 { throw!(anyhow!(
         "Content-Disposition value implausibly long"
       )) }
+
+      // todo: replace with mailparse?
+      // (not in side, dep on charset not in sid)
+      // also seems to box for all the bits
+
       // This let's us pretend it's a mime type, so we can use mime::Mime
       let disposition = format!("dummy/{}", disposition);
 
@@ -71,7 +82,60 @@ pub fn process_component<'b>(warnings: &mut Warnings,
     };
   }
 
-  Some(Component { name: part_name.unwrap_or(expected), payload_start: rhs })
+  Some(Component { name: part_name.unwrap_or(expected), payload: rhs })
+}
+
+pub struct ComponentIterator<'b> {
+  at_boundary: &'b [u8],
+  boundary_finder: BoundaryFinder,
+}
+
+#[derive(Error,Debug)]
+#[error("missing mime multipart boundary")]
+pub struct MissingBoundary;
+
+impl<'b> ComponentIterator<'b> {
+  #[throws(MissingBoundary)]
+  pub fn resume_mid_component(buf: &'b [u8], boundary_finder: BoundaryFinder)
+                              -> (&'b [u8], Self) {
+    let next_boundary = boundary_finder.find(buf).ok_or(MissingBoundary)?;
+    let part = &buf[0..next_boundary];
+    let part = Self::payload_trim(part);
+    (part, ComponentIterator {
+      at_boundary: &buf[next_boundary..],
+      boundary_finder,
+    })
+  }
+
+  fn payload_trim(payload: &[u8]) -> &[u8] {
+    payload.strip_suffix(b"\r").unwrap_or(payload)
+  }
+
+  #[throws(AE)]
+  pub fn next(&mut self, warnings: &mut Warnings, expected: PartName)
+              -> Option<Component<'b>> {
+    if self.at_boundary.is_empty() { return None }
+
+    let mut comp = match {
+      let boundary_len = self.boundary_finder.needle().len();
+      process_boundary(warnings,
+                       &self.at_boundary[boundary_len..],
+                       expected)?
+    } {
+      None => {
+        self.at_boundary = &self.at_boundary[0..0];
+        return None;
+      },
+      Some(c) => c,
+    };
+
+    let next_boundary = self.boundary_finder.find(&comp.payload)
+      .ok_or(MissingBoundary)?;
+
+    comp.payload = Self::payload_trim(&comp.payload[0..next_boundary]);
+    self.at_boundary = &self.at_boundary[next_boundary..];
+    Some(comp)
+  }
 }
 
 pub struct MetadataFieldIterator<'b> {
@@ -86,6 +150,37 @@ impl<'b> MetadataFieldIterator<'b> {
     last: Some(0),
     iter: memchr::Memchr::new(b'\n', buf),
   } }
+
+  #[throws(AE)]
+  pub fn need_next(&mut self) -> &'b str
+  {
+    self.next().ok_or_else(|| anyhow!("missing"))??
+  }
+
+  #[throws(AE)]
+  pub fn need_parse<T>(&mut self) -> T
+  where T: FromStr,
+        AE: From<T::Err>,
+  {
+    self.parse()?.ok_or_else(|| anyhow!("missing"))?
+  }
+
+  #[throws(AE)]
+  pub fn parse<T>(&mut self) -> Option<T>
+  where T: FromStr,
+        AE: From<T::Err>,
+  {
+    let s = if let Some(r) = self.next() { r? } else { return None };
+    Some(s.parse()?)
+  }
+
+  pub fn remaining_bytes_len(&self) -> usize {
+    if let Some(last) = self.last {
+      self.buf.len() - last
+    } else {
+      0
+    }
+  }
 }
                                       
 impl<'b> Iterator for MetadataFieldIterator<'b> {
@@ -101,3 +196,4 @@ impl<'b> Iterator for MetadataFieldIterator<'b> {
     Some(s)
   }
 }
+impl<'b> std::iter::FusedIterator for MetadataFieldIterator<'b> { }