common_multipart_rfc7578/client_.rs
1// Copyright 2017 rust-multipart-rfc7578 Developers
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7//
8
9use crate::{
10 boundary::{BoundaryGenerator, RandomAsciiGenerator},
11 error::Error,
12};
13use bytes::{BufMut, BytesMut};
14use futures_core::Stream;
15use futures_util::io::{AllowStdIo, AsyncRead, Cursor};
16use http::{
17 self,
18 header::{self, HeaderName},
19 request::{Builder, Request},
20};
21use mime::{self, Mime};
22use std::{
23 fmt::Display,
24 fs::File,
25 io::{self, Read},
26 iter::Peekable,
27 path::Path,
28 pin::Pin,
29 task::{Context, Poll},
30 vec::IntoIter,
31};
32
33static CONTENT_DISPOSITION: HeaderName = header::CONTENT_DISPOSITION;
34static CONTENT_TYPE: HeaderName = header::CONTENT_TYPE;
35
36/// Async streamable Multipart body.
37pub struct Body<'a> {
38 /// The amount of data to write with each chunk.
39 buf: BytesMut,
40
41 /// The active reader.
42 current: Option<Box<dyn 'a + AsyncRead + Send + Sync + Unpin>>,
43
44 /// The parts as an iterator. When the iterator stops
45 /// yielding, the body is fully written.
46 parts: Peekable<IntoIter<Part<'a>>>,
47
48 /// The multipart boundary.
49 boundary: String,
50}
51
52impl<'a> Body<'a> {
53 /// Writes a CLRF.
54 fn write_crlf(&mut self) {
55 self.buf.put_slice(b"\r\n");
56 }
57
58 /// Implements section 4.1.
59 ///
60 /// [See](https://tools.ietf.org/html/rfc7578#section-4.1).
61 fn write_boundary(&mut self) {
62 self.buf.put_slice(b"--");
63 self.buf.put_slice(self.boundary.as_bytes());
64 }
65
66 /// Writes the last form boundary.
67 ///
68 /// [See](https://tools.ietf.org/html/rfc2046#section-5.1).
69 fn write_final_boundary(&mut self) {
70 self.write_boundary();
71 self.buf.put_slice(b"--");
72 }
73
74 /// Writes the Content-Disposition, and Content-Type headers.
75 fn write_headers(&mut self, part: &Part) {
76 self.write_crlf();
77 self.buf.put_slice(CONTENT_TYPE.as_ref());
78 self.buf.put_slice(b": ");
79 self.buf.put_slice(part.content_type.as_bytes());
80 self.write_crlf();
81 self.buf.put_slice(CONTENT_DISPOSITION.as_ref());
82 self.buf.put_slice(b": ");
83 self.buf.put_slice(part.content_disposition.as_bytes());
84 self.write_crlf();
85 self.write_crlf();
86 }
87}
88
89impl<'a> Stream for Body<'a> {
90 type Item = Result<BytesMut, Error>;
91
92 /// Iterate over each form part, and write it out.
93 fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
94 let body = self.get_mut();
95
96 match body.current {
97 None => {
98 if let Some(part) = body.parts.next() {
99 body.write_boundary();
100 body.write_headers(&part);
101
102 let read: Box<dyn AsyncRead + Send + Sync + Unpin> = match part.inner {
103 Inner::Read(read) => Box::new(AllowStdIo::new(read)),
104 Inner::AsyncRead(read) => read,
105 Inner::Text(s) => Box::new(Cursor::new(s)),
106 };
107
108 body.current = Some(read);
109
110 cx.waker().wake_by_ref();
111
112 Poll::Ready(Some(Ok(body.buf.split())))
113 } else {
114 // No current part, and no parts left means there is nothing
115 // left to write.
116 //
117 Poll::Ready(None)
118 }
119 }
120 Some(ref mut read) => {
121 // Reserve some space to read the next part
122 body.buf.reserve(256);
123 let len_before = body.buf.len();
124
125 // Init the remaining capacity to 0, and get a mut slice to it
126 body.buf.resize(body.buf.capacity(), 0);
127 let slice = &mut body.buf.as_mut()[len_before..];
128
129 match Pin::new(read).poll_read(cx, slice) {
130 Poll::Pending => {
131 body.buf.truncate(len_before);
132 Poll::Pending
133 }
134 // Read some data.
135 Poll::Ready(Ok(bytes_read)) => {
136 body.buf.truncate(len_before + bytes_read);
137
138 if bytes_read == 0 {
139 // EOF: No data left to read. Get ready to move onto write the next part.
140 body.current = None;
141 body.write_crlf();
142 if body.parts.peek().is_none() {
143 // If there is no next part, write the final boundary
144 body.write_final_boundary();
145 body.write_crlf();
146 }
147 }
148
149 Poll::Ready(Some(Ok(body.buf.split())))
150 }
151 // Error reading from underlying stream.
152 Poll::Ready(Err(e)) => {
153 body.buf.truncate(len_before);
154 Poll::Ready(Some(Err(Error::ContentRead(e))))
155 }
156 }
157 }
158 }
159 }
160}
161
162/// Implements the multipart/form-data media type as described by
163/// RFC 7578.
164///
165/// [See](https://tools.ietf.org/html/rfc7578#section-1).
166pub struct Form<'a> {
167 parts: Vec<Part<'a>>,
168
169 /// The auto-generated boundary as described by 4.1.
170 ///
171 /// [See](https://tools.ietf.org/html/rfc7578#section-4.1).
172 boundary: String,
173}
174
175impl<'a> Default for Form<'a> {
176 /// Creates a new form with the default boundary generator.
177 #[inline]
178 fn default() -> Form<'a> {
179 Form::new::<RandomAsciiGenerator>()
180 }
181}
182
183impl<'a> Form<'a> {
184 /// Creates a new form with the specified boundary generator function.
185 ///
186 /// # Examples
187 ///
188 /// ```
189 /// # use common_multipart_rfc7578::client::multipart::{
190 /// # self,
191 /// # BoundaryGenerator
192 /// # };
193 /// #
194 /// struct TestGenerator;
195 ///
196 /// impl BoundaryGenerator for TestGenerator {
197 /// fn generate_boundary() -> String {
198 /// "test".to_string()
199 /// }
200 /// }
201 ///
202 /// let form = multipart::Form::new::<TestGenerator>();
203 /// ```
204 #[inline]
205 pub fn new<G>() -> Form<'a>
206 where
207 G: BoundaryGenerator,
208 {
209 Form {
210 parts: vec![],
211 boundary: G::generate_boundary(),
212 }
213 }
214
215 /// Adds a text part to the Form.
216 ///
217 /// # Examples
218 ///
219 /// ```
220 /// use common_multipart_rfc7578::client::multipart;
221 ///
222 /// let mut form = multipart::Form::default();
223 ///
224 /// form.add_text("text", "Hello World!");
225 /// form.add_text("more", String::from("Hello Universe!"));
226 /// ```
227 pub fn add_text<N, T>(&mut self, name: N, text: T)
228 where
229 N: Display,
230 T: Into<String>,
231 {
232 self.parts.push(Part::new::<_, String>(
233 Inner::Text(text.into()),
234 name,
235 None,
236 None,
237 ))
238 }
239
240 /// Adds a readable part to the Form.
241 ///
242 /// # Examples
243 ///
244 /// ```
245 /// use common_multipart_rfc7578::client::multipart;
246 /// use std::io::Cursor;
247 ///
248 /// let bytes = Cursor::new("Hello World!");
249 /// let mut form = multipart::Form::default();
250 ///
251 /// form.add_reader("input", bytes);
252 /// ```
253 pub fn add_reader<F, R>(&mut self, name: F, read: R)
254 where
255 F: Display,
256 R: 'a + Read + Send + Sync + Unpin,
257 {
258 let read = Box::new(read);
259
260 self.parts
261 .push(Part::new::<_, String>(Inner::Read(read), name, None, None));
262 }
263
264 /// Adds a readable part to the Form.
265 ///
266 /// # Examples
267 ///
268 /// ```
269 /// use common_multipart_rfc7578::client::multipart;
270 /// use futures_util::io::Cursor;
271 ///
272 /// let bytes = Cursor::new("Hello World!");
273 /// let mut form = multipart::Form::default();
274 ///
275 /// form.add_async_reader("input", bytes);
276 /// ```
277 pub fn add_async_reader<F, R>(&mut self, name: F, read: R)
278 where
279 F: Display,
280 R: 'a + AsyncRead + Send + Sync + Unpin,
281 {
282 let read = Box::new(read);
283
284 self.parts.push(Part::new::<_, String>(
285 Inner::AsyncRead(read),
286 name,
287 None,
288 None,
289 ));
290 }
291
292 /// Adds a file, and attempts to derive the mime type.
293 ///
294 /// # Examples
295 ///
296 /// ```
297 /// use common_multipart_rfc7578::client::multipart;
298 ///
299 /// let mut form = multipart::Form::default();
300 ///
301 /// form.add_file("file", format!("../{}", file!()))
302 /// .expect("file to exist");
303 /// ```
304 pub fn add_file<P, F>(&mut self, name: F, path: P) -> io::Result<()>
305 where
306 P: AsRef<Path>,
307 F: Display,
308 {
309 self._add_file(name, path, None)
310 }
311
312 /// Adds a file with the specified mime type to the form.
313 /// If the mime type isn't specified, a mime type will try to
314 /// be derived.
315 ///
316 /// # Examples
317 ///
318 /// ```
319 /// use common_multipart_rfc7578::client::multipart;
320 ///
321 /// let mut form = multipart::Form::default();
322 ///
323 /// form.add_file_with_mime("data", "test.csv", mime::TEXT_CSV);
324 /// ```
325 pub fn add_file_with_mime<P, F>(&mut self, name: F, path: P, mime: Mime) -> io::Result<()>
326 where
327 P: AsRef<Path>,
328 F: Display,
329 {
330 self._add_file(name, path, Some(mime))
331 }
332
333 /// Internal method for adding a file part to the form.
334 fn _add_file<P, F>(&mut self, name: F, path: P, mime: Option<Mime>) -> io::Result<()>
335 where
336 P: AsRef<Path>,
337 F: Display,
338 {
339 let f = File::open(&path)?;
340 let mime = mime.or_else(|| mime_guess::from_path(&path).first());
341
342 // Early return if the file metadata could not be accessed. This MIGHT
343 // not be an error, if the file could be opened.
344 let meta = f.metadata()?;
345
346 if !meta.is_file() {
347 // If the path is not a file, it can't be uploaded because there
348 // is no content.
349
350 return Err(io::Error::new(
351 io::ErrorKind::InvalidInput,
352 "expected a file not directory",
353 ));
354 }
355
356 let read = Box::new(f);
357
358 self.parts.push(Part::new(
359 Inner::Read(read),
360 name,
361 mime,
362 Some(path.as_ref().as_os_str().to_string_lossy()),
363 ));
364
365 Ok(())
366 }
367
368 /// Adds a readable part to the Form as a file.
369 ///
370 /// # Examples
371 ///
372 /// ```
373 /// use common_multipart_rfc7578::client::multipart;
374 /// use std::io::Cursor;
375 ///
376 /// let bytes = Cursor::new("Hello World!");
377 /// let mut form = multipart::Form::default();
378 ///
379 /// form.add_reader_file("input", bytes, "filename.txt");
380 /// ```
381 pub fn add_reader_file<F, G, R>(&mut self, name: F, read: R, filename: G)
382 where
383 F: Display,
384 G: Into<String>,
385 R: 'a + Read + Send + Sync + Unpin,
386 {
387 let read = Box::new(read);
388
389 self.parts.push(Part::new::<_, String>(
390 Inner::Read(read),
391 name,
392 None,
393 Some(filename.into()),
394 ));
395 }
396
397 /// Adds a readable part to the Form as a file.
398 ///
399 /// # Examples
400 ///
401 /// ```
402 /// use common_multipart_rfc7578::client::multipart;
403 /// use futures_util::io::Cursor;
404 ///
405 /// let bytes = Cursor::new("Hello World!");
406 /// let mut form = multipart::Form::default();
407 ///
408 /// form.add_async_reader_file("input", bytes, "filename.txt");
409 /// ```
410 pub fn add_async_reader_file<F, G, R>(&mut self, name: F, read: R, filename: G)
411 where
412 F: Display,
413 G: Into<String>,
414 R: 'a + AsyncRead + Send + Sync + Unpin,
415 {
416 let read = Box::new(read);
417
418 self.parts.push(Part::new::<_, String>(
419 Inner::AsyncRead(read),
420 name,
421 None,
422 Some(filename.into()),
423 ));
424 }
425
426 /// Adds a readable part to the Form as a file with a specified mime.
427 ///
428 /// # Examples
429 ///
430 /// ```
431 /// use common_multipart_rfc7578::client::multipart;
432 /// use std::io::Cursor;
433 ///
434 /// let bytes = Cursor::new("Hello World!");
435 /// let mut form = multipart::Form::default();
436 ///
437 /// form.add_reader_file_with_mime("input", bytes, "filename.txt", mime::TEXT_PLAIN);
438 /// ```
439 pub fn add_reader_file_with_mime<F, G, R>(&mut self, name: F, read: R, filename: G, mime: Mime)
440 where
441 F: Display,
442 G: Into<String>,
443 R: 'a + Read + Send + Sync + Unpin,
444 {
445 let read = Box::new(read);
446
447 self.parts.push(Part::new::<_, String>(
448 Inner::Read(read),
449 name,
450 Some(mime),
451 Some(filename.into()),
452 ));
453 }
454
455 /// Adds a readable part to the Form as a file with a specified mime.
456 ///
457 /// # Examples
458 ///
459 /// ```
460 /// use common_multipart_rfc7578::client::multipart;
461 /// use futures_util::io::Cursor;
462 ///
463 /// let bytes = Cursor::new("Hello World!");
464 /// let mut form = multipart::Form::default();
465 ///
466 /// form.add_async_reader_file_with_mime("input", bytes, "filename.txt", mime::TEXT_PLAIN);
467 /// ```
468 pub fn add_async_reader_file_with_mime<F, G, R>(
469 &mut self,
470 name: F,
471 read: R,
472 filename: G,
473 mime: Mime,
474 ) where
475 F: Display,
476 G: Into<String>,
477 R: 'a + AsyncRead + Send + Sync + Unpin,
478 {
479 let read = Box::new(read);
480
481 self.parts.push(Part::new::<_, String>(
482 Inner::AsyncRead(read),
483 name,
484 Some(mime),
485 Some(filename.into()),
486 ));
487 }
488
489 /// Updates a request instance with the multipart Content-Type header
490 /// and the payload data.
491 ///
492 /// # Examples
493 ///
494 /// ```
495 /// use hyper::{Method, Request};
496 /// use hyper_multipart_rfc7578::client::multipart;
497 ///
498 /// let mut req_builder = Request::post("http://localhost:80/upload");
499 /// let mut form = multipart::Form::default();
500 ///
501 /// form.add_text("text", "Hello World!");
502 /// let req = form.set_body::<multipart::Body>(req_builder).unwrap();
503 /// ```
504 pub fn set_body<B>(self, req: Builder) -> Result<Request<B>, http::Error>
505 where
506 B: From<Body<'a>>,
507 {
508 self.set_body_convert::<B, B>(req)
509 }
510
511 /// Updates a request instance with the multipart Content-Type header
512 /// and the payload data.
513 ///
514 /// Allows converting body into an intermediate type.
515 ///
516 /// # Examples
517 ///
518 /// ```
519 /// use http_body_util::BodyDataStream;
520 /// use hyper::{Method, Request};
521 /// use hyper_multipart_rfc7578::client::multipart;
522 ///
523 /// let mut req_builder = Request::post("http://localhost:80/upload");
524 /// let mut form = multipart::Form::default();
525 ///
526 /// form.add_text("text", "Hello World!");
527 /// let req = form
528 /// .set_body_convert::<multipart::Body, multipart::Body>(req_builder)
529 /// .unwrap();
530 /// ```
531 // Dev note: I am not sure this function is useful anymore, I could not fix the test
532 // with something besides an identity transform.
533 pub fn set_body_convert<B, I>(self, req: Builder) -> Result<Request<B>, http::Error>
534 where
535 I: From<Body<'a>> + Into<B>,
536 {
537 req.header(&CONTENT_TYPE, self.content_type().as_str())
538 .body(I::from(Body::from(self)).into())
539 }
540
541 pub fn content_type(&self) -> String {
542 format!("multipart/form-data; boundary={}", &self.boundary)
543 }
544}
545
546impl<'a> From<Form<'a>> for Body<'a> {
547 /// Turns a `Form` into a multipart `Body`.
548 fn from(form: Form<'a>) -> Self {
549 Body {
550 buf: BytesMut::with_capacity(2048),
551 current: None,
552 parts: form.parts.into_iter().peekable(),
553 boundary: form.boundary,
554 }
555 }
556}
557
558/// One part of a body delimited by a boundary line.
559///
560/// [See RFC2046 5.1](https://tools.ietf.org/html/rfc2046#section-5.1).
561pub struct Part<'a> {
562 inner: Inner<'a>,
563
564 /// Each part can include a Content-Type header field. If this
565 /// is not specified, it defaults to "text/plain", or
566 /// "application/octet-stream" for file data.
567 ///
568 /// [See](https://tools.ietf.org/html/rfc7578#section-4.4)
569 content_type: String,
570
571 /// Each part must contain a Content-Disposition header field.
572 ///
573 /// [See](https://tools.ietf.org/html/rfc7578#section-4.2).
574 content_disposition: String,
575}
576
577impl<'a> Part<'a> {
578 /// Internal method to build a new Part instance. Sets the disposition type,
579 /// content-type, and the disposition parameters for name, and optionally
580 /// for filename.
581 ///
582 /// Per [4.3](https://tools.ietf.org/html/rfc7578#section-4.3), if multiple
583 /// files need to be specified for one form field, they can all be specified
584 /// with the same name parameter.
585 fn new<N, F>(inner: Inner<'a>, name: N, mime: Option<Mime>, filename: Option<F>) -> Part<'a>
586 where
587 N: Display,
588 F: Display,
589 {
590 // `name` disposition parameter is required. It should correspond to the
591 // name of a form field.
592 //
593 // [See 4.2](https://tools.ietf.org/html/rfc7578#section-4.2)
594 //
595 let mut disposition_params = vec![format!("name=\"{}\"", name)];
596
597 // `filename` can be supplied for files, but is totally optional.
598 //
599 // [See 4.2](https://tools.ietf.org/html/rfc7578#section-4.2)
600 //
601 if let Some(filename) = filename {
602 disposition_params.push(format!("filename=\"{}\"", filename));
603 }
604
605 let content_type = format!("{}", mime.unwrap_or_else(|| inner.default_content_type()));
606
607 Part {
608 inner,
609 content_type,
610 content_disposition: format!("form-data; {}", disposition_params.join("; ")),
611 }
612 }
613}
614
615enum Inner<'a> {
616 /// The `Read` and `AsyncRead` variants captures multiple cases.
617 ///
618 /// * The first is it supports uploading a file, which is explicitly
619 /// described in RFC 7578.
620 ///
621 /// * The second (which is not described by RFC 7578), is it can handle
622 /// arbitrary input streams (for example, a server response).
623 /// Any arbitrary input stream is automatically considered a file,
624 /// and assigned the corresponding content type if not explicitly
625 /// specified.
626 Read(Box<dyn 'a + Read + Send + Sync + Unpin>),
627
628 AsyncRead(Box<dyn 'a + AsyncRead + Send + Sync + Unpin>),
629
630 /// The `String` variant handles "text/plain" form data payloads.
631 Text(String),
632}
633
634impl<'a> Inner<'a> {
635 /// Returns the default Content-Type header value as described in section 4.4.
636 ///
637 /// [See](https://tools.ietf.org/html/rfc7578#section-4.4)
638 fn default_content_type(&self) -> Mime {
639 match *self {
640 Inner::Read(_) | Inner::AsyncRead(_) => mime::APPLICATION_OCTET_STREAM,
641 Inner::Text(_) => mime::TEXT_PLAIN,
642 }
643 }
644}
645
646#[cfg(test)]
647mod tests {
648 use super::{Body, Form};
649 use crate::error::Error;
650 use bytes::BytesMut;
651 use futures_util::TryStreamExt;
652 use std::{
653 io::Cursor,
654 path::{Path, PathBuf},
655 };
656
657 async fn form_output(form: Form<'_>) -> String {
658 let result: Result<BytesMut, Error> = Body::from(form).try_concat().await;
659
660 assert!(result.is_ok());
661
662 let bytes = result.unwrap();
663 let data = std::str::from_utf8(bytes.as_ref()).unwrap();
664
665 data.into()
666 }
667
668 fn test_file_path() -> PathBuf {
669 // common/src/data/test.txt
670 Path::new(env!("CARGO_MANIFEST_DIR"))
671 .join("src")
672 .join("data")
673 .join("test.txt")
674 }
675
676 #[tokio::test]
677 async fn add_text_returns_expected_result() {
678 let mut form = Form::default();
679
680 form.add_text("test", "Hello World!");
681
682 let data = form_output(form).await;
683
684 assert!(data.contains("Hello World!"));
685 }
686
687 #[tokio::test]
688 async fn add_reader_returns_expected_result() {
689 let bytes = Cursor::new("Hello World!");
690 let mut form = Form::default();
691
692 form.add_reader("input", bytes);
693
694 let data = form_output(form).await;
695
696 assert!(data.contains("Hello World!"));
697 }
698
699 #[tokio::test]
700 async fn add_file_returns_expected_result() {
701 let mut form = Form::default();
702
703 assert!(form.add_file("test_file.txt", test_file_path()).is_ok());
704
705 let data = form_output(form).await;
706
707 assert!(data.contains("This is a test file!"));
708 assert!(data.contains("text/plain"));
709 }
710
711 #[tokio::test]
712 async fn add_file_with_mime_returns_expected_result() {
713 let mut form = Form::default();
714
715 assert!(form
716 .add_file_with_mime("test_file.txt", test_file_path(), mime::TEXT_CSV)
717 .is_ok());
718
719 let data = form_output(form).await;
720
721 assert!(data.contains("This is a test file!"));
722 assert!(data.contains("text/csv"));
723 }
724
725 struct FixedBoundary;
726 impl crate::boundary::BoundaryGenerator for FixedBoundary {
727 fn generate_boundary() -> String {
728 "boundary".to_owned()
729 }
730 }
731
732 #[tokio::test]
733 async fn test_form_body_stream() {
734 let mut form = Form::new::<FixedBoundary>();
735 // Text fields
736 form.add_text("name1", "value1");
737 form.add_text("name2", "value2");
738
739 // Reader field
740 form.add_reader("input", Cursor::new("Hello World!"));
741
742 let result: BytesMut = Body::from(form).try_concat().await.unwrap();
743
744 assert_eq!(
745 result.as_ref(),
746 [
747 b"--boundary\r\n".as_ref(),
748 b"content-type: text/plain\r\n".as_ref(),
749 b"content-disposition: form-data; name=\"name1\"\r\n".as_ref(),
750 b"\r\n".as_ref(),
751 b"value1\r\n".as_ref(),
752 b"--boundary\r\n".as_ref(),
753 b"content-type: text/plain\r\n".as_ref(),
754 b"content-disposition: form-data; name=\"name2\"\r\n".as_ref(),
755 b"\r\n".as_ref(),
756 b"value2\r\n".as_ref(),
757 b"--boundary\r\n".as_ref(),
758 b"content-type: application/octet-stream\r\n".as_ref(),
759 b"content-disposition: form-data; name=\"input\"\r\n".as_ref(),
760 b"\r\n".as_ref(),
761 b"Hello World!\r\n".as_ref(),
762 b"--boundary--\r\n".as_ref(),
763 ]
764 .into_iter()
765 .flatten()
766 .copied()
767 .collect::<Vec<u8>>()
768 );
769 }
770
771 #[tokio::test]
772 async fn test_content_type_header_format() {
773 use http::Request;
774
775 let mut form = Form::new::<FixedBoundary>();
776 // Text fields
777 form.add_text("name1", "value1");
778 form.add_text("name2", "value2");
779
780 let builder = Request::builder();
781 let body = form.set_body::<Body>(builder).unwrap();
782
783 assert_eq!(
784 body.headers().get("Content-Type").unwrap().as_bytes(),
785 b"multipart/form-data; boundary=boundary",
786 )
787 }
788}