dusks_reqwest/blocking/
multipart.rs

1//! multipart/form-data
2//!
3//! To send a `multipart/form-data` body, a [`Form`] is built up, adding
4//! fields or customized [`Part`]s, and then calling the
5//! [`multipart`][builder] method on the `RequestBuilder`.
6//!
7//! # Example
8//!
9//! ```
10//! use reqwest::blocking::multipart;
11//!
12//! # fn run() -> Result<(), Box<dyn std::error::Error>> {
13//! let form = multipart::Form::new()
14//!     // Adding just a simple text field...
15//!     .text("username", "seanmonstar")
16//!     // And a file...
17//!     .file("photo", "/path/to/photo.png")?;
18//!
19//! // Customize all the details of a Part if needed...
20//! let bio = multipart::Part::text("hallo peeps")
21//!     .file_name("bio.txt")
22//!     .mime_str("text/plain")?;
23//!
24//! // Add the custom part to our form...
25//! let form = form.part("biography", bio);
26//!
27//! // And finally, send the form
28//! let client = reqwest::blocking::Client::new();
29//! let resp = client
30//!     .post("http://localhost:8080/user")
31//!     .multipart(form)
32//!     .send()?;
33//! # Ok(())
34//! # }
35//! # fn main() {}
36//! ```
37//!
38//! [builder]: ../struct.RequestBuilder.html#method.multipart
39use std::borrow::Cow;
40use std::fmt;
41use std::fs::File;
42use std::io::{self, Cursor, Read};
43use std::path::Path;
44
45use mime_guess::{self, Mime};
46
47use super::Body;
48use crate::async_impl::multipart::{FormParts, PartMetadata, PartProps};
49use crate::header::HeaderMap;
50
51/// A multipart/form-data request.
52pub struct Form {
53    inner: FormParts<Part>,
54}
55
56/// A field in a multipart form.
57pub struct Part {
58    meta: PartMetadata,
59    value: Body,
60}
61
62impl Default for Form {
63    fn default() -> Self {
64        Self::new()
65    }
66}
67
68impl Form {
69    /// Creates a new Form without any content.
70    pub fn new() -> Form {
71        Form {
72            inner: FormParts::new(),
73        }
74    }
75
76    /// Get the boundary that this form will use.
77    #[inline]
78    pub fn boundary(&self) -> &str {
79        self.inner.boundary()
80    }
81
82    /// Add a data field with supplied name and value.
83    ///
84    /// # Examples
85    ///
86    /// ```
87    /// let form = reqwest::blocking::multipart::Form::new()
88    ///     .text("username", "seanmonstar")
89    ///     .text("password", "secret");
90    /// ```
91    pub fn text<T, U>(self, name: T, value: U) -> Form
92    where
93        T: Into<Cow<'static, str>>,
94        U: Into<Cow<'static, str>>,
95    {
96        self.part(name, Part::text(value))
97    }
98
99    /// Adds a file field.
100    ///
101    /// The path will be used to try to guess the filename and mime.
102    ///
103    /// # Examples
104    ///
105    /// ```no_run
106    /// # fn run() -> std::io::Result<()> {
107    /// let files = reqwest::blocking::multipart::Form::new()
108    ///     .file("key", "/path/to/file")?;
109    /// # Ok(())
110    /// # }
111    /// ```
112    ///
113    /// # Errors
114    ///
115    /// Errors when the file cannot be opened.
116    pub fn file<T, U>(self, name: T, path: U) -> io::Result<Form>
117    where
118        T: Into<Cow<'static, str>>,
119        U: AsRef<Path>,
120    {
121        Ok(self.part(name, Part::file(path)?))
122    }
123
124    /// Adds a customized Part.
125    pub fn part<T>(self, name: T, part: Part) -> Form
126    where
127        T: Into<Cow<'static, str>>,
128    {
129        self.with_inner(move |inner| inner.part(name, part))
130    }
131
132    /// Configure this `Form` to percent-encode using the `path-segment` rules.
133    pub fn percent_encode_path_segment(self) -> Form {
134        self.with_inner(|inner| inner.percent_encode_path_segment())
135    }
136
137    /// Configure this `Form` to percent-encode using the `attr-char` rules.
138    pub fn percent_encode_attr_chars(self) -> Form {
139        self.with_inner(|inner| inner.percent_encode_attr_chars())
140    }
141
142    /// Configure this `Form` to skip percent-encoding
143    pub fn percent_encode_noop(self) -> Form {
144        self.with_inner(|inner| inner.percent_encode_noop())
145    }
146
147    pub(crate) fn reader(self) -> Reader {
148        Reader::new(self)
149    }
150
151    // If predictable, computes the length the request will have
152    // The length should be preditable if only String and file fields have been added,
153    // but not if a generic reader has been added;
154    pub(crate) fn compute_length(&mut self) -> Option<u64> {
155        self.inner.compute_length()
156    }
157
158    fn with_inner<F>(self, func: F) -> Self
159    where
160        F: FnOnce(FormParts<Part>) -> FormParts<Part>,
161    {
162        Form {
163            inner: func(self.inner),
164        }
165    }
166}
167
168impl fmt::Debug for Form {
169    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
170        self.inner.fmt_fields("Form", f)
171    }
172}
173
174impl Part {
175    /// Makes a text parameter.
176    pub fn text<T>(value: T) -> Part
177    where
178        T: Into<Cow<'static, str>>,
179    {
180        let body = match value.into() {
181            Cow::Borrowed(slice) => Body::from(slice),
182            Cow::Owned(string) => Body::from(string),
183        };
184        Part::new(body)
185    }
186
187    /// Makes a new parameter from arbitrary bytes.
188    pub fn bytes<T>(value: T) -> Part
189    where
190        T: Into<Cow<'static, [u8]>>,
191    {
192        let body = match value.into() {
193            Cow::Borrowed(slice) => Body::from(slice),
194            Cow::Owned(vec) => Body::from(vec),
195        };
196        Part::new(body)
197    }
198
199    /// Adds a generic reader.
200    ///
201    /// Does not set filename or mime.
202    pub fn reader<T: Read + Send + 'static>(value: T) -> Part {
203        Part::new(Body::new(value))
204    }
205
206    /// Adds a generic reader with known length.
207    ///
208    /// Does not set filename or mime.
209    pub fn reader_with_length<T: Read + Send + 'static>(value: T, length: u64) -> Part {
210        Part::new(Body::sized(value, length))
211    }
212
213    /// Makes a file parameter.
214    ///
215    /// # Errors
216    ///
217    /// Errors when the file cannot be opened.
218    pub fn file<T: AsRef<Path>>(path: T) -> io::Result<Part> {
219        let path = path.as_ref();
220        let file_name = path
221            .file_name()
222            .map(|filename| filename.to_string_lossy().into_owned());
223        let ext = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
224        let mime = mime_guess::from_ext(ext).first_or_octet_stream();
225        let file = File::open(path)?;
226        let field = Part::new(Body::from(file)).mime(mime);
227
228        Ok(if let Some(file_name) = file_name {
229            field.file_name(file_name)
230        } else {
231            field
232        })
233    }
234
235    fn new(value: Body) -> Part {
236        Part {
237            meta: PartMetadata::new(),
238            value,
239        }
240    }
241
242    /// Tries to set the mime of this part.
243    pub fn mime_str(self, mime: &str) -> crate::Result<Part> {
244        Ok(self.mime(mime.parse().map_err(crate::error::builder)?))
245    }
246
247    // Re-export when mime 0.4 is available, with split MediaType/MediaRange.
248    fn mime(self, mime: Mime) -> Part {
249        self.with_inner(move |inner| inner.mime(mime))
250    }
251
252    /// Sets the filename, builder style.
253    pub fn file_name<T>(self, filename: T) -> Part
254    where
255        T: Into<Cow<'static, str>>,
256    {
257        self.with_inner(move |inner| inner.file_name(filename))
258    }
259
260    /// Sets custom headers for the part.
261    pub fn headers(self, headers: HeaderMap) -> Part {
262        self.with_inner(move |inner| inner.headers(headers))
263    }
264
265    fn with_inner<F>(self, func: F) -> Self
266    where
267        F: FnOnce(PartMetadata) -> PartMetadata,
268    {
269        Part {
270            meta: func(self.meta),
271            value: self.value,
272        }
273    }
274}
275
276impl fmt::Debug for Part {
277    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
278        let mut dbg = f.debug_struct("Part");
279        dbg.field("value", &self.value);
280        self.meta.fmt_fields(&mut dbg);
281        dbg.finish()
282    }
283}
284
285impl PartProps for Part {
286    fn value_len(&self) -> Option<u64> {
287        self.value.len()
288    }
289
290    fn metadata(&self) -> &PartMetadata {
291        &self.meta
292    }
293}
294
295pub(crate) struct Reader {
296    form: Form,
297    active_reader: Option<Box<dyn Read + Send>>,
298}
299
300impl fmt::Debug for Reader {
301    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
302        f.debug_struct("Reader").field("form", &self.form).finish()
303    }
304}
305
306impl Reader {
307    fn new(form: Form) -> Reader {
308        let mut reader = Reader {
309            form,
310            active_reader: None,
311        };
312        reader.next_reader();
313        reader
314    }
315
316    fn next_reader(&mut self) {
317        self.active_reader = if !self.form.inner.fields.is_empty() {
318            // We need to move out of the vector here because we are consuming the field's reader
319            let (name, field) = self.form.inner.fields.remove(0);
320            let boundary = Cursor::new(format!("--{}\r\n", self.form.boundary()));
321            let header = Cursor::new({
322                // Try to use cached headers created by compute_length
323                let mut h = if !self.form.inner.computed_headers.is_empty() {
324                    self.form.inner.computed_headers.remove(0)
325                } else {
326                    self.form
327                        .inner
328                        .percent_encoding
329                        .encode_headers(&name, field.metadata())
330                };
331                h.extend_from_slice(b"\r\n\r\n");
332                h
333            });
334            let reader = boundary
335                .chain(header)
336                .chain(field.value.into_reader())
337                .chain(Cursor::new("\r\n"));
338            // According to https://tools.ietf.org/html/rfc2046#section-5.1.1
339            // the very last field has a special boundary
340            if !self.form.inner.fields.is_empty() {
341                Some(Box::new(reader))
342            } else {
343                Some(Box::new(reader.chain(Cursor::new(format!(
344                    "--{}--\r\n",
345                    self.form.boundary()
346                )))))
347            }
348        } else {
349            None
350        }
351    }
352}
353
354impl Read for Reader {
355    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
356        let mut total_bytes_read = 0usize;
357        let mut last_read_bytes;
358        loop {
359            match self.active_reader {
360                Some(ref mut reader) => {
361                    last_read_bytes = reader.read(&mut buf[total_bytes_read..])?;
362                    total_bytes_read += last_read_bytes;
363                    if total_bytes_read == buf.len() {
364                        return Ok(total_bytes_read);
365                    }
366                }
367                None => return Ok(total_bytes_read),
368            };
369            if last_read_bytes == 0 && !buf.is_empty() {
370                self.next_reader();
371            }
372        }
373    }
374}
375
376#[cfg(test)]
377mod tests {
378    use super::*;
379
380    #[test]
381    fn form_empty() {
382        let mut output = Vec::new();
383        let mut form = Form::new();
384        let length = form.compute_length();
385        form.reader().read_to_end(&mut output).unwrap();
386        assert_eq!(output, b"");
387        assert_eq!(length.unwrap(), 0);
388    }
389
390    #[test]
391    fn read_to_end() {
392        let mut output = Vec::new();
393        let mut form = Form::new()
394            .part("reader1", Part::reader(std::io::empty()))
395            .part("key1", Part::text("value1"))
396            .part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
397            .part("reader2", Part::reader(std::io::empty()))
398            .part("key3", Part::text("value3").file_name("filename"));
399        form.inner.boundary = "boundary".to_string();
400        let length = form.compute_length();
401        let expected = "--boundary\r\n\
402             Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
403             \r\n\
404             --boundary\r\n\
405             Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
406             value1\r\n\
407             --boundary\r\n\
408             Content-Disposition: form-data; name=\"key2\"\r\n\
409             Content-Type: image/bmp\r\n\r\n\
410             value2\r\n\
411             --boundary\r\n\
412             Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
413             \r\n\
414             --boundary\r\n\
415             Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
416             value3\r\n--boundary--\r\n";
417        form.reader().read_to_end(&mut output).unwrap();
418        // These prints are for debug purposes in case the test fails
419        println!(
420            "START REAL\n{}\nEND REAL",
421            std::str::from_utf8(&output).unwrap()
422        );
423        println!("START EXPECTED\n{expected}\nEND EXPECTED");
424        assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
425        assert!(length.is_none());
426    }
427
428    #[test]
429    fn read_to_end_with_length() {
430        let mut output = Vec::new();
431        let mut form = Form::new()
432            .text("key1", "value1")
433            .part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
434            .part("key3", Part::text("value3").file_name("filename"));
435        form.inner.boundary = "boundary".to_string();
436        let length = form.compute_length();
437        let expected = "--boundary\r\n\
438             Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
439             value1\r\n\
440             --boundary\r\n\
441             Content-Disposition: form-data; name=\"key2\"\r\n\
442             Content-Type: image/bmp\r\n\r\n\
443             value2\r\n\
444             --boundary\r\n\
445             Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
446             value3\r\n--boundary--\r\n";
447        form.reader().read_to_end(&mut output).unwrap();
448        // These prints are for debug purposes in case the test fails
449        println!(
450            "START REAL\n{}\nEND REAL",
451            std::str::from_utf8(&output).unwrap()
452        );
453        println!("START EXPECTED\n{expected}\nEND EXPECTED");
454        assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
455        assert_eq!(length.unwrap(), expected.len() as u64);
456    }
457
458    #[test]
459    fn read_to_end_with_header() {
460        let mut output = Vec::new();
461        let mut part = Part::text("value2").mime(mime::IMAGE_BMP);
462        let mut headers = HeaderMap::new();
463        headers.insert("Hdr3", "/a/b/c".parse().unwrap());
464        part = part.headers(headers);
465        let mut form = Form::new().part("key2", part);
466        form.inner.boundary = "boundary".to_string();
467        let expected = "--boundary\r\n\
468                        Content-Disposition: form-data; name=\"key2\"\r\n\
469                        Content-Type: image/bmp\r\n\
470                        hdr3: /a/b/c\r\n\
471                        \r\n\
472                        value2\r\n\
473                        --boundary--\r\n";
474        form.reader().read_to_end(&mut output).unwrap();
475        // These prints are for debug purposes in case the test fails
476        println!(
477            "START REAL\n{}\nEND REAL",
478            std::str::from_utf8(&output).unwrap()
479        );
480        println!("START EXPECTED\n{expected}\nEND EXPECTED");
481        assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
482    }
483}