chiark / gitweb /
server: wip, parse disposition
[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_start: &'b [u8],
11 }
12
13 #[derive(Debug)]
14 #[allow(non_camel_case_types)]
15 pub enum PartName { m, d, Other }
16
17 #[throws(AE)]
18 pub fn process_component<'b>(warnings: &mut Warnings,
19                              after_leader: &'b [u8], expected: PartName)
20                              -> Option<Component<'b>> {
21   let rhs = after_leader;
22   let mut rhs =
23        if let Some(rhs) = rhs.strip_prefix(b"\r\n") { rhs         }
24   else if let Some(_  ) = rhs.strip_prefix(b"--"  ) { return None }
25   else if let Some(rhs) = rhs.strip_prefix(b"\n"  ) { rhs         }
26   else { throw!(anyhow!("invalid multipart delimiter")) };
27
28   let mut part_name = None;
29
30   loop {
31     // RHS points to the start of a header line
32     let nl = memchr::memchr(b'\n', rhs)
33       .ok_or_else(|| anyhow!("part headers truncated"))?;
34     let l = &rhs[0..nl]; rhs = &rhs[nl+1..];
35     if l == b"\r" || l == b"" { break } // end of headers
36     if l.starts_with(b"--") { throw!(anyhow!("boundary in part headers")) }
37
38     match (||{
39       let l = str::from_utf8(l).context("interpret part headers as utf-8")?;
40
41       let (_, disposition) = if let Some(y) =
42         regex_captures!(r#"^Content-Disposition[ \t]*:[ \t]*(.*)$"#i, l) { y }
43         else { return Ok(()) };
44
45       let disposition = disposition.trim_end();
46       if disposition.len() >= 100 { throw!(anyhow!(
47         "Content-Disposition value implausibly long"
48       )) }
49       // This let's us pretend it's a mime type, so we can use mime::Mime
50       let disposition = format!("dummy/{}", disposition);
51
52       let disposition: mime::Mime = disposition.parse()
53         .context("parse Content-Disposition")?;
54       let name = disposition.get_param("name")
55         .ok_or_else(|| anyhow!(r#"find "name" in Content-Disposition"#))?;
56
57       let name = match name.as_ref() {
58         "m" => PartName::m,
59         "d" => PartName::d,
60         _   => PartName::Other,
61       };
62
63       if let Some(_) = mem::replace(&mut part_name, Some(name)) {
64         throw!(anyhow!(r#"multiple "name"s in Content-Disposition(s)"#))
65       }
66       Ok::<_,AE>(())
67     })() {
68       Err(e) => warnings.add(&e)?,
69       Ok(()) => { },
70     };
71   }
72
73   Some(Component { name: part_name.unwrap_or(expected), payload_start: rhs })
74 }