Err(ReadLimitedError::Hyper(e)) => throw!(e),
};
- eprintln!("boundary={:?} initial={:?}", boundary, initial);
+ let finder = memmem::Finder::new(&boundary);
+ let mut find_iter = finder.find_iter(&initial);
+
+ let start = if initial.starts_with(&boundary[1..]) { boundary.len()-1 }
+ else if let Some(start) = find_iter.next() { start + boundary.len() }
+ else { throw!(anyhow!("initial boundary not found")) };
+
+/*
+
+ if {
+
+initial[start..].strip_prefixstarts_with(b"\n")
+ || initial[start..].starts_with(b"\r\n")*/
+
+ eprintln!("boundary={:?} initial={:?} start={}",
+ boundary, initial, start);
Ok::<_,AE>(())
}.await {
--- /dev/null
+// Copyright 2021 Ian Jackson and contributors to Hippotat
+// SPDX-License-Identifier: GPL-3.0-or-later
+// There is NO WARRANTY.
+
+use crate::prelude::*;
+
+pub struct Component<'b> {
+ pub name: PartName,
+ pub payload_start: &'b [u8],
+}
+
+#[derive(Debug)]
+#[allow(non_camel_case_types)]
+pub enum PartName { m, d, Other }
+
+#[throws(AE)]
+pub fn process_component<'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 }
+ else if let Some(_ ) = rhs.strip_prefix(b"--" ) { return None }
+ else if let Some(rhs) = rhs.strip_prefix(b"\n" ) { rhs }
+ else { throw!(anyhow!("invalid multipart delimiter")) };
+
+ let mut part_name = None;
+
+ loop {
+ // RHS points to the start of a header line
+ let nl = memchr::memchr(b'\n', rhs)
+ .ok_or_else(|| anyhow!("part headers truncated"))?;
+ let l = &rhs[0..nl]; rhs = &rhs[nl+1..];
+ if l == b"\r" || l == b"" { break } // end of headers
+ if l.starts_with(b"--") { throw!(anyhow!("boundary in part headers")) }
+
+ match (||{
+ let l = str::from_utf8(l).context("interpret part headers as utf-8")?;
+
+ let (_, disposition) = if let Some(y) =
+ regex_captures!(r#"^Content-Disposition[ \t]*:[ \t]*(.*)$"#i, l) { y }
+ else { return Ok(()) };
+
+ let disposition: mime::Mime = disposition.parse()
+ .context("parse Content-Disposition")?;
+ let name = disposition.get_param("name")
+ .ok_or_else(|| anyhow!(r#"find "name" in Content-Disposition"#))?;
+
+ let name = match name.as_ref() {
+ "m" => PartName::m,
+ "d" => PartName::d,
+ _ => PartName::Other,
+ };
+
+ if let Some(_) = mem::replace(&mut part_name, Some(name)) {
+ throw!(anyhow!(r#"multiple "name"s in Content-Disposition(s)"#))
+ }
+ Ok::<_,AE>(())
+ })() {
+ Err(e) => warnings.add(&e)?,
+ Ok(()) => { },
+ };
+ }
+
+ Some(Component { name: part_name.unwrap_or(expected), payload_start: rhs })
+}
pub use lazy_regex::{regex_captures, regex_is_match, regex_replace_all};
pub use lazy_static::lazy_static;
pub use log::{trace, debug, info, warn, error};
+pub use memchr::memmem;
pub use structopt::StructOpt;
pub use thiserror::Error;
pub use tokio::io::{AsyncBufReadExt, AsyncWriteExt};
}))
.context("set error handler")?;
}
+
+const MAX_WARNINGS: usize = 15;
+
+pub struct Warnings {
+ pub warnings: Vec<String>,
+}
+
+impl Warnings {
+ #[throws(AE)]
+ pub fn add(&mut self, e: &dyn Display) {
+ if self.warnings.len() >= MAX_WARNINGS {
+ throw!(anyhow!("too many warnings"))
+ }
+ self.warnings.push(e.to_string());
+ }
+}