go_http/mime/
multipart.rs1use std::collections::HashMap;
5use std::io::{self, BufRead, BufReader, Read, Write};
6
7use crate::header::Header;
8use super::MimeError;
9
10#[allow(dead_code)]
17pub struct Reader<R: Read> {
18 inner: BufReader<R>,
19 boundary: String,
20 delim: Vec<u8>,
22 done: bool,
23}
24
25impl<R: Read> Reader<R> {
26 pub fn new(inner: R, boundary: &str) -> Self {
28 let delim = format!("--{boundary}").into_bytes();
29 Self {
30 inner: BufReader::new(inner),
31 boundary: boundary.to_owned(),
32 delim,
33 done: false,
34 }
35 }
36
37 pub fn next_part(&mut self) -> Result<Option<Part<'_>>, MimeError> {
40 if self.done {
41 return Ok(None);
42 }
43
44 loop {
46 let mut line = String::new();
47 let n = self.inner
48 .read_line(&mut line)
49 .map_err(|e| MimeError::Other(e.to_string()))?;
50 if n == 0 {
51 self.done = true;
52 return Ok(None);
53 }
54 let trimmed = line.trim_end_matches(['\r', '\n']);
55 if trimmed.as_bytes() == self.delim.as_slice() {
56 let header = read_part_headers(&mut self.inner)?;
58 return Ok(Some(Part {
59 header,
60 boundary: self.delim.clone(),
61 inner: &mut self.inner as *mut BufReader<R> as *mut (),
62 _marker: std::marker::PhantomData,
63 }));
64 }
65 let mut close = self.delim.clone();
67 close.extend_from_slice(b"--");
68 if trimmed.as_bytes() == close.as_slice() {
69 self.done = true;
70 return Ok(None);
71 }
72 }
74 }
75
76 pub fn read_form(&mut self, max_memory: i64) -> Result<Form, MimeError> {
83 let mut form = Form::default();
84 let mut mem_used: i64 = 0;
85
86 while let Some(mut part) = self.next_part()? {
87 let cd = part.header.get("Content-Disposition").unwrap_or("").to_owned();
88 let (_, params) = super::parse_media_type(&cd)
89 .unwrap_or_else(|_| ("".into(), HashMap::new()));
90
91 let name = params.get("name").cloned().unwrap_or_default();
92 let filename = params.get("filename").cloned();
93
94 let mut body = Vec::new();
95 part.read_to_end(&mut body)
96 .map_err(|e| MimeError::Other(e.to_string()))?;
97
98 if let Some(fname) = filename {
99 let size = body.len() as i64;
100 form.file
101 .entry(name)
102 .or_default()
103 .push(FileHeader {
104 filename: fname,
105 header: part.header.clone(),
106 size,
107 content: body,
108 });
109 } else {
110 mem_used += body.len() as i64;
111 if max_memory >= 0 && mem_used > max_memory {
112 return Err(MimeError::Other("multipart: message too large".into()));
113 }
114 if let Ok(s) = String::from_utf8(body) {
115 form.value.entry(name).or_default().push(s);
116 }
117 }
118 }
119
120 Ok(form)
121 }
122}
123
124fn read_part_headers<R: Read>(r: &mut BufReader<R>) -> Result<Header, MimeError> {
125 let mut h = Header::new();
126 loop {
127 let mut line = String::new();
128 let n = r
129 .read_line(&mut line)
130 .map_err(|e| MimeError::Other(e.to_string()))?;
131 if n == 0 {
132 break;
133 }
134 let trimmed = line.trim_end_matches(['\r', '\n']);
135 if trimmed.is_empty() {
136 break;
137 }
138 if let Some(colon) = trimmed.find(':') {
139 let name = trimmed[..colon].trim();
140 let value = trimmed[colon + 1..].trim();
141 h.add(name, value);
142 }
143 }
144 Ok(h)
145}
146
147#[allow(dead_code)]
153pub struct Part<'a> {
154 pub header: Header,
155 boundary: Vec<u8>,
156 inner: *mut (),
157 _marker: std::marker::PhantomData<&'a mut ()>,
158}
159
160impl<'a> Read for Part<'a> {
161 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
162 if buf.is_empty() {
166 return Ok(0);
167 }
168 unsafe {
169 let r = &mut *(self.inner as *mut BufReader<std::io::Empty>);
170 r.read(&mut buf[..1])
171 }
172 }
173}
174
175#[derive(Debug, Default)]
181pub struct Form {
182 pub value: HashMap<String, Vec<String>>,
184 pub file: HashMap<String, Vec<FileHeader>>,
186}
187
188#[derive(Debug)]
190pub struct FileHeader {
191 pub filename: String,
192 pub header: Header,
193 pub size: i64,
194 content: Vec<u8>,
195}
196
197impl FileHeader {
198 pub fn open(&self) -> impl Read + '_ {
200 std::io::Cursor::new(self.content.as_slice())
201 }
202}
203
204#[allow(dead_code)]
211pub struct Writer<W: Write> {
212 inner: W,
213 boundary: String,
214 last: bool,
215}
216
217impl<W: Write> Writer<W> {
218 pub fn new(inner: W) -> Self {
219 let boundary = random_boundary();
220 Self { inner, boundary, last: false }
221 }
222
223 pub fn boundary(&self) -> &str {
224 &self.boundary
225 }
226
227 pub fn form_data_content_type(&self) -> String {
229 format!("multipart/form-data; boundary={}", self.boundary)
230 }
231
232 pub fn create_part(&mut self, header: Header) -> io::Result<PartWriter<'_, W>> {
234 write!(self.inner, "--{}\r\n", self.boundary)?;
235 header.write_to(&mut self.inner)?;
236 self.inner.write_all(b"\r\n")?;
237 Ok(PartWriter { inner: &mut self.inner })
238 }
239
240 pub fn create_form_field(&mut self, fieldname: &str) -> io::Result<PartWriter<'_, W>> {
242 let mut h = Header::new();
243 h.set(
244 "Content-Disposition",
245 format!("form-data; name=\"{fieldname}\""),
246 );
247 self.create_part(h)
248 }
249
250 pub fn create_form_file(
252 &mut self,
253 fieldname: &str,
254 filename: &str,
255 ) -> io::Result<PartWriter<'_, W>> {
256 let mut h = Header::new();
257 h.set(
258 "Content-Disposition",
259 format!("form-data; name=\"{fieldname}\"; filename=\"{filename}\""),
260 );
261 h.set("Content-Type", "application/octet-stream");
262 self.create_part(h)
263 }
264
265 pub fn close(mut self) -> io::Result<W> {
267 write!(self.inner, "--{}--\r\n", self.boundary)?;
268 self.inner.flush()?;
269 Ok(self.inner)
270 }
271}
272
273pub struct PartWriter<'a, W: Write> {
275 inner: &'a mut W,
276}
277
278impl<'a, W: Write> Write for PartWriter<'a, W> {
279 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
280 self.inner.write(buf)
281 }
282 fn flush(&mut self) -> io::Result<()> {
283 self.inner.flush()
284 }
285}
286
287fn random_boundary() -> String {
288 use std::time::{SystemTime, UNIX_EPOCH};
289 let t = SystemTime::now()
290 .duration_since(UNIX_EPOCH)
291 .unwrap_or_default()
292 .subsec_nanos();
293 format!("{t:016x}x")
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299 use std::io::Write;
300
301 #[test]
302 fn writer_produces_valid_output() {
303 let mut buf = Vec::new();
304 {
305 let mut w = Writer::new(&mut buf);
306 let boundary = w.boundary().to_owned();
307 {
308 let mut part = w.create_form_field("greeting").unwrap();
309 part.write_all(b"Hello").unwrap();
310 }
311 w.close().unwrap();
312 let s = String::from_utf8(buf.clone()).unwrap();
314 assert!(s.contains(&format!("--{boundary}")));
315 assert!(s.contains("Content-Disposition: form-data; name=\"greeting\""));
316 assert!(s.contains("Hello"));
317 assert!(s.contains(&format!("--{boundary}--")));
318 }
319 }
320}