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_start: &'b [u8],
14 #[derive(Eq,PartialEq,Ord,PartialOrd,Hash)]
15 #[allow(non_camel_case_types)]
16 pub enum PartName { m, d, Other }
19 pub fn process_component<'b>(warnings: &mut Warnings,
20 after_leader: &'b [u8], expected: PartName)
21 -> Option<Component<'b>> {
22 let rhs = after_leader;
24 if let Some(rhs) = rhs.strip_prefix(b"\r\n") { rhs }
25 else if let Some(_ ) = rhs.strip_prefix(b"--" ) { return None }
26 else if let Some(rhs) = rhs.strip_prefix(b"\n" ) { rhs }
27 else { throw!(anyhow!("invalid multipart delimiter")) };
29 let mut part_name = None;
32 // RHS points to the start of a header line
33 let nl = memchr::memchr(b'\n', rhs)
34 .ok_or_else(|| anyhow!("part headers truncated"))?;
35 let l = &rhs[0..nl]; rhs = &rhs[nl+1..];
36 if l == b"\r" || l == b"" { break } // end of headers
37 if l.starts_with(b"--") { throw!(anyhow!("boundary in part headers")) }
40 let l = str::from_utf8(l).context("interpret part headers as utf-8")?;
42 let (_, disposition) = if let Some(y) =
43 regex_captures!(r#"^Content-Disposition[ \t]*:[ \t]*(.*)$"#i, l) { y }
44 else { return Ok(()) };
46 let disposition = disposition.trim_end();
47 if disposition.len() >= 100 { throw!(anyhow!(
48 "Content-Disposition value implausibly long"
50 // This let's us pretend it's a mime type, so we can use mime::Mime
51 let disposition = format!("dummy/{}", disposition);
53 let disposition: mime::Mime = disposition.parse()
54 .context("parse Content-Disposition")?;
55 let name = disposition.get_param("name")
56 .ok_or_else(|| anyhow!(r#"find "name" in Content-Disposition"#))?;
58 let name = match name.as_ref() {
64 if let Some(_) = mem::replace(&mut part_name, Some(name)) {
65 throw!(anyhow!(r#"multiple "name"s in Content-Disposition(s)"#))
69 Err(e) => warnings.add(&e)?,
74 Some(Component { name: part_name.unwrap_or(expected), payload_start: rhs })
77 pub struct MetadataFieldIterator<'b> {
80 iter: memchr::Memchr<'b>,
83 impl<'b> MetadataFieldIterator<'b> {
84 pub fn new(buf: &'b [u8]) -> Self { Self {
87 iter: memchr::Memchr::new(b'\n', buf),
91 pub fn need_next(&mut self) -> &'b str
93 self.next().ok_or_else(|| anyhow!("missing"))??
97 pub fn need_parse<T>(&mut self) -> T
101 self.parse()?.ok_or_else(|| anyhow!("missing"))?
105 pub fn parse<T>(&mut self) -> Option<T>
109 let s = if let Some(r) = self.next() { r? } else { return None };
113 pub fn remaining_bytes_len(&self) -> usize {
114 if let Some(last) = self.last {
115 self.buf.len() - last
122 impl<'b> Iterator for MetadataFieldIterator<'b> {
123 type Item = Result<&'b str, std::str::Utf8Error>;
124 fn next(&mut self) -> Option<Result<&'b str, std::str::Utf8Error>> {
125 let last = self.last?;
126 let (s, last) = match self.iter.next() {
127 Some(nl) => (&self.buf[last..nl], Some(nl+1)),
128 None => (&self.buf[last..], None),
131 let s = str::from_utf8(s).map(|s| s.trim());
135 impl<'b> std::iter::FusedIterator for MetadataFieldIterator<'b> { }