1pub mod error;
9
10#[cfg(test)]
11mod tests;
12
13pub use error::Error;
14
15use buf_read_ext::BufReadExt;
16use http::header::{HeaderMap, HeaderName, HeaderValue};
17use mime::Mime;
18use std::fs::File;
19use std::io::{BufRead, BufReader, Read, Write};
20use std::ops::Drop;
21use std::path::{Path, PathBuf};
22use std::str::FromStr;
23use textnonce::TextNonce;
24
25#[derive(Clone, Debug, PartialEq)]
27pub struct Part {
28 pub headers: HeaderMap,
29 pub body: Vec<u8>,
30}
31impl Part {
32 pub fn content_type(&self) -> Option<Mime> {
34 match self.headers.get("content-type") {
35 Some(ct) => match ct.to_str() {
36 Ok(value) => match Mime::from_str(value) {
37 Ok(value) => Some(value),
38 Err(_) => None,
39 },
40 Err(_) => None,
41 },
42 None => None,
43 }
44 }
45}
46
47#[derive(Clone, Debug, PartialEq)]
50pub struct FilePart {
51 pub headers: HeaderMap,
53 pub path: PathBuf,
55 pub size: Option<usize>,
58 tempdir: Option<PathBuf>,
60}
61impl FilePart {
62 pub fn new(headers: HeaderMap, path: &Path) -> FilePart {
63 FilePart {
64 headers,
65 path: path.to_owned(),
66 size: None,
67 tempdir: None,
68 }
69 }
70
71 pub fn do_not_delete_on_drop(&mut self) {
74 self.tempdir = None;
75 }
76
77 pub fn create(headers: HeaderMap) -> Result<FilePart, Error> {
80 let mut path = tempfile::Builder::new()
82 .prefix("mime_multipart")
83 .tempdir()?
84 .into_path();
85 let tempdir = Some(path.clone());
86 path.push(TextNonce::sized_urlsafe(32).unwrap().into_string());
87 Ok(FilePart {
88 headers,
89 path,
90 size: None,
91 tempdir,
92 })
93 }
94
95 pub fn filename(&self) -> Result<Option<String>, Error> {
98 match self.headers.get("content-disposition") {
99 Some(cd) => get_content_disposition_filename(cd),
100 None => Ok(None),
101 }
102 }
103
104 pub fn content_type(&self) -> Option<Mime> {
106 match self.headers.get("content-type") {
107 Some(ct) => match ct.to_str() {
108 Ok(value) => match Mime::from_str(value) {
109 Ok(value) => Some(value),
110 Err(_) => None,
111 },
112 Err(_) => None,
113 },
114 None => None,
115 }
116 }
117}
118impl Drop for FilePart {
119 fn drop(&mut self) {
120 if self.tempdir.is_some() {
121 let _ = std::fs::remove_file(&self.path);
122 let _ = std::fs::remove_dir(self.tempdir.as_ref().unwrap());
123 }
124 }
125}
126
127#[derive(Clone, Debug)]
130pub enum Node {
131 Part(Part),
133 File(FilePart),
135 Multipart((HeaderMap, Vec<Node>)),
137}
138
139pub fn read_multipart<S: Read>(stream: &mut S, always_use_files: bool) -> Result<Vec<Node>, Error> {
150 let mut reader = BufReader::with_capacity(4096, stream);
151
152 let mut buf: Vec<u8> = Vec::new();
153
154 let (_, found) = reader.stream_until_token(b"\r\n\r\n", &mut buf)?;
155 if !found {
156 return Err(Error::EofInMainHeaders);
157 }
158
159 buf.extend(b"\r\n\r\n".iter().cloned());
161
162 let mut header_memory = [httparse::EMPTY_HEADER; 64];
164 let headers = match httparse::parse_headers(&buf, &mut header_memory) {
165 Ok(httparse::Status::Complete((_, raw_headers))) => {
166 let mut headers = HeaderMap::new();
167 for header in raw_headers {
168 if header.value.is_empty() {
169 break;
170 }
171 let trim = header
172 .value
173 .iter()
174 .rev()
175 .take_while(|&&x| x == b' ')
176 .count();
177 let value = &header.value[..header.value.len() - trim];
178
179 let header_value = match HeaderValue::from_bytes(value) {
180 Ok(value) => value,
181 Err(_) => return Err(Error::InvalidHeaderNameOrValue),
182 };
183
184 let header_name = header.name.to_owned();
185 let header_name = match HeaderName::from_str(&header_name) {
186 Ok(value) => value,
187 Err(_) => return Err(Error::InvalidHeaderNameOrValue),
188 };
189 headers.append(header_name, header_value);
190 }
191 Ok(headers)
192 }
193 Ok(httparse::Status::Partial) => Err(Error::PartialHeaders),
194 Err(err) => Err(From::from(err)),
195 }?;
196
197 inner(&mut reader, &headers, always_use_files)
198}
199
200pub fn read_multipart_body<S: Read>(
211 stream: &mut S,
212 headers: &HeaderMap,
213 always_use_files: bool,
214) -> Result<Vec<Node>, Error> {
215 let mut reader = BufReader::with_capacity(4096, stream);
216 inner(&mut reader, headers, always_use_files)
217}
218
219fn inner<R: BufRead>(
220 reader: &mut R,
221 headers: &HeaderMap,
222 always_use_files: bool,
223) -> Result<Vec<Node>, Error> {
224 let mut nodes: Vec<Node> = Vec::new();
225 let mut buf: Vec<u8> = Vec::new();
226
227 let boundary = get_multipart_boundary(headers)?;
228
229 let (_, found) = reader.stream_until_token(&boundary, &mut buf)?;
231 if !found {
232 return Err(Error::EofBeforeFirstBoundary);
233 }
234
235 let (lt, ltlt, lt_boundary) = {
238 let peeker = reader.fill_buf()?;
239 if peeker.len() > 1 && &peeker[..2] == b"\r\n" {
240 let mut output = Vec::with_capacity(2 + boundary.len());
241 output.push(b'\r');
242 output.push(b'\n');
243 output.extend(boundary.clone());
244 (vec![b'\r', b'\n'], vec![b'\r', b'\n', b'\r', b'\n'], output)
245 } else if !peeker.is_empty() && peeker[0] == b'\n' {
246 let mut output = Vec::with_capacity(1 + boundary.len());
247 output.push(b'\n');
248 output.extend(boundary.clone());
249 (vec![b'\n'], vec![b'\n', b'\n'], output)
250 } else {
251 return Err(Error::NoCrLfAfterBoundary);
252 }
253 };
254
255 loop {
256 {
258 let peeker = reader.fill_buf()?;
259 if peeker.len() >= 2 && &peeker[..2] == b"--" {
260 return Ok(nodes);
261 }
262 }
263
264 let (_, found) = reader.stream_until_token(<, &mut buf)?;
266 if !found {
267 return Err(Error::NoCrLfAfterBoundary);
268 }
269
270 buf.truncate(0); let (_, found) = reader.stream_until_token(<lt, &mut buf)?;
273 if !found {
274 return Err(Error::EofInPartHeaders);
275 }
276
277 buf.extend(ltlt.iter().cloned());
279
280 let part_headers = {
282 let mut header_memory = [httparse::EMPTY_HEADER; 4];
283 match httparse::parse_headers(&buf, &mut header_memory) {
284 Ok(httparse::Status::Complete((_, raw_headers))) => {
285 let mut headers = HeaderMap::new();
286 for header in raw_headers {
287 if header.value.is_empty() {
288 break;
289 }
290 let trim = header
291 .value
292 .iter()
293 .rev()
294 .take_while(|&&x| x == b' ')
295 .count();
296 let value = &header.value[..header.value.len() - trim];
297
298 let header_value = match HeaderValue::from_bytes(value) {
299 Ok(value) => value,
300 Err(_) => return Err(Error::InvalidHeaderNameOrValue),
301 };
302
303 let header_name = header.name.to_owned();
304 let header_name = match HeaderName::from_str(&header_name) {
305 Ok(value) => value,
306 Err(_) => return Err(Error::InvalidHeaderNameOrValue),
307 };
308 headers.append(header_name, header_value);
309 }
310 Ok(headers)
311 }
312 Ok(httparse::Status::Partial) => Err(Error::PartialHeaders),
313 Err(err) => Err(From::from(err)),
314 }?
315 };
316
317 let nested = {
319 match part_headers.get("content-type") {
320 Some(ct) => match ct.to_str() {
321 Ok(value) => match Mime::from_str(value) {
322 Ok(mime) => mime.type_() == mime::MULTIPART,
323 Err(_) => return Err(Error::HeaderValueNotMime),
324 },
325 Err(err) => return Err(Error::ToStr(err)),
326 },
327 None => false,
328 }
329 };
330 if nested {
331 let inner_nodes = inner(reader, &part_headers, always_use_files)?;
333 nodes.push(Node::Multipart((part_headers, inner_nodes)));
334 continue;
335 }
336
337 let is_file = always_use_files || {
338 match part_headers.get("content-disposition") {
339 Some(content) => match content.to_str() {
340 Ok(value) => value.contains("attachment") || value.contains("filename"),
341 Err(err) => return Err(Error::ToStr(err)),
342 },
343 None => false,
344 }
345 };
346 if is_file {
347 let mut filepart = FilePart::create(part_headers)?;
349 let mut file = File::create(filepart.path.clone())?;
350
351 let (read, found) = reader.stream_until_token(<_boundary, &mut file)?;
353 if !found {
354 return Err(Error::EofInFile);
355 }
356 filepart.size = Some(read);
357
358 nodes.push(Node::File(filepart));
363 } else {
364 buf.truncate(0); let (_, found) = reader.stream_until_token(<_boundary, &mut buf)?;
366 if !found {
367 return Err(Error::EofInPart);
368 }
369
370 nodes.push(Node::Part(Part {
371 headers: part_headers,
372 body: buf.clone(),
373 }));
374 }
375 }
376}
377
378pub fn get_multipart_boundary(headers: &HeaderMap) -> Result<Vec<u8>, Error> {
380 let mime = match headers.get("content-type") {
382 Some(ct) => match ct.to_str() {
383 Ok(value) => match Mime::from_str(value) {
384 Ok(value) => value,
385 Err(_) => return Err(Error::HeaderValueNotMime),
386 },
387 Err(err) => return Err(Error::ToStr(err)),
388 },
389 None => return Err(Error::NoRequestContentType),
390 };
391 let top_level = mime.type_();
392
393 if top_level != mime::MULTIPART {
394 return Err(Error::NotMultipart);
395 }
396
397 match mime.get_param(mime::BOUNDARY) {
398 None => Err(Error::BoundaryNotSpecified),
399 Some(content) => {
400 let mut boundary = vec![];
401 boundary.extend(b"--".iter().cloned());
402 boundary.extend(content.to_string().as_bytes());
403 Ok(boundary)
404 }
405 }
406}
407
408#[inline]
409fn get_content_disposition_filename(cd: &HeaderValue) -> Result<Option<String>, Error> {
410 match cd.to_str() {
411 Ok(value) => match value.contains("filename") {
412 true => match value.find("filename=") {
413 Some(index) => {
414 let start = index + "filename=".len();
415 Ok(Some(
416 value.get(start..).unwrap().trim_matches('\"').to_owned(),
417 ))
418 }
419 None => match value.find("filename*=UTF-8''") {
420 Some(index) => {
421 let start = index + "filename*=UTF-8''".len();
422 Ok(Some(
423 value.get(start..).unwrap().trim_matches('\"').to_owned(),
424 ))
425 }
426 None => Ok(None),
427 },
428 },
429 false => Ok(None),
430 },
431 Err(err) => Err(Error::ToStr(err)),
432 }
433}
434
435pub fn generate_boundary() -> Vec<u8> {
438 TextNonce::sized(68)
439 .unwrap()
440 .into_string()
441 .into_bytes()
442 .iter()
443 .map(|&ch| {
444 if ch == b'=' {
445 b'-'
446 } else if ch == b'/' {
447 b'.'
448 } else {
449 ch
450 }
451 })
452 .collect()
453}
454
455trait WriteAllCount {
457 fn write_all_count(&mut self, buf: &[u8]) -> std::io::Result<usize>;
458}
459impl<T: Write> WriteAllCount for T {
460 fn write_all_count(&mut self, buf: &[u8]) -> std::io::Result<usize> {
461 self.write_all(buf)?;
462 Ok(buf.len())
463 }
464}
465
466pub fn write_multipart<S: Write>(
471 stream: &mut S,
472 boundary: &[u8],
473 nodes: &Vec<Node>,
474) -> Result<usize, Error> {
475 let mut count: usize = 0;
476
477 for node in nodes {
478 count += stream.write_all_count(b"--")?;
480 count += stream.write_all_count(boundary)?;
481 count += stream.write_all_count(b"\r\n")?;
482
483 match *node {
484 Node::Part(ref part) => {
485 for header in part.headers.iter() {
487 count += stream.write_all_count(header.0.as_str().as_bytes())?;
488 count += stream.write_all_count(b": ")?;
489 count += stream.write_all_count(header.1.as_bytes())?;
490 count += stream.write_all_count(b"\r\n")?;
491 }
492
493 count += stream.write_all_count(b"\r\n")?;
495
496 count += stream.write_all_count(&part.body)?;
498 }
499 Node::File(ref filepart) => {
500 for header in filepart.headers.iter() {
502 count += stream.write_all_count(header.0.as_str().as_bytes())?;
503 count += stream.write_all_count(b": ")?;
504 count += stream.write_all_count(header.1.as_bytes())?;
505 count += stream.write_all_count(b"\r\n")?;
506 }
507
508 count += stream.write_all_count(b"\r\n")?;
510
511 let mut file = File::open(&filepart.path)?;
513 count += std::io::copy(&mut file, stream)? as usize;
514 }
515 Node::Multipart((ref headers, ref subnodes)) => {
516 let boundary = get_multipart_boundary(headers)?;
518
519 for header in headers.iter() {
521 count += stream.write_all_count(header.0.as_str().as_bytes())?;
522 count += stream.write_all_count(b": ")?;
523 count += stream.write_all_count(header.1.as_bytes())?;
524 count += stream.write_all_count(b"\r\n")?;
525 }
526
527 count += stream.write_all_count(b"\r\n")?;
529
530 count += write_multipart(stream, &boundary, subnodes)?;
532 }
533 }
534
535 count += stream.write_all_count(b"\r\n")?;
537 }
538
539 count += stream.write_all_count(b"--")?;
541 count += stream.write_all_count(boundary)?;
542 count += stream.write_all_count(b"--")?;
543
544 Ok(count)
545}
546
547pub fn write_chunk<S: Write>(stream: &mut S, chunk: &[u8]) -> Result<(), ::std::io::Error> {
548 write!(stream, "{:x}\r\n", chunk.len())?;
549 stream.write_all(chunk)?;
550 stream.write_all(b"\r\n")?;
551 Ok(())
552}
553
554pub fn write_multipart_chunked<S: Write>(
558 stream: &mut S,
559 boundary: &[u8],
560 nodes: &Vec<Node>,
561) -> Result<(), Error> {
562 for node in nodes {
563 write_chunk(stream, b"--")?;
565 write_chunk(stream, boundary)?;
566 write_chunk(stream, b"\r\n")?;
567
568 match *node {
569 Node::Part(ref part) => {
570 for header in part.headers.iter() {
572 write_chunk(stream, header.0.as_str().as_bytes())?;
573 write_chunk(stream, b": ")?;
574 write_chunk(stream, header.1.as_bytes())?;
575 write_chunk(stream, b"\r\n")?;
576 }
577
578 write_chunk(stream, b"\r\n")?;
580
581 write_chunk(stream, &part.body)?;
583 }
584 Node::File(ref filepart) => {
585 for header in filepart.headers.iter() {
587 write_chunk(stream, header.0.as_str().as_bytes())?;
588 write_chunk(stream, b": ")?;
589 write_chunk(stream, header.1.as_bytes())?;
590 write_chunk(stream, b"\r\n")?;
591 }
592
593 write_chunk(stream, b"\r\n")?;
595
596 let metadata = std::fs::metadata(&filepart.path)?;
598 write!(stream, "{:x}\r\n", metadata.len())?;
599
600 let mut file = File::open(&filepart.path)?;
602 std::io::copy(&mut file, stream)?;
603 stream.write_all(b"\r\n")?;
604 }
605 Node::Multipart((ref headers, ref subnodes)) => {
606 let boundary = get_multipart_boundary(headers)?;
608
609 for header in headers.iter() {
611 write_chunk(stream, header.0.as_str().as_bytes())?;
612 write_chunk(stream, b": ")?;
613 write_chunk(stream, header.1.as_bytes())?;
614 write_chunk(stream, b"\r\n")?;
615 }
616
617 write_chunk(stream, b"\r\n")?;
619
620 write_multipart_chunked(stream, &boundary, subnodes)?;
622 }
623 }
624
625 write_chunk(stream, b"\r\n")?;
627 }
628
629 write_chunk(stream, b"--")?;
631 write_chunk(stream, boundary)?;
632 write_chunk(stream, b"--")?;
633
634 write_chunk(stream, b"")?;
636
637 Ok(())
638}