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}