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 form = 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    /// Produce a reader over the multipart form data.
152    pub fn into_reader(self) -> impl Read {
153        self.reader()
154    }
155
156    // If predictable, computes the length the request will have
157    // The length should be predictable if only String and file fields have been added,
158    // but not if a generic reader has been added;
159    pub(crate) fn compute_length(&mut self) -> Option<u64> {
160        self.inner.compute_length()
161    }
162
163    fn with_inner<F>(self, func: F) -> Self
164    where
165        F: FnOnce(FormParts<Part>) -> FormParts<Part>,
166    {
167        Form {
168            inner: func(self.inner),
169        }
170    }
171}
172
173impl fmt::Debug for Form {
174    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
175        self.inner.fmt_fields("Form", f)
176    }
177}
178
179impl Part {
180    /// Makes a text parameter.
181    pub fn text<T>(value: T) -> Part
182    where
183        T: Into<Cow<'static, str>>,
184    {
185        let body = match value.into() {
186            Cow::Borrowed(slice) => Body::from(slice),
187            Cow::Owned(string) => Body::from(string),
188        };
189        Part::new(body)
190    }
191
192    /// Makes a new parameter from arbitrary bytes.
193    pub fn bytes<T>(value: T) -> Part
194    where
195        T: Into<Cow<'static, [u8]>>,
196    {
197        let body = match value.into() {
198            Cow::Borrowed(slice) => Body::from(slice),
199            Cow::Owned(vec) => Body::from(vec),
200        };
201        Part::new(body)
202    }
203
204    /// Adds a generic reader.
205    ///
206    /// Does not set filename or mime.
207    pub fn reader<T: Read + Send + 'static>(value: T) -> Part {
208        Part::new(Body::new(value))
209    }
210
211    /// Adds a generic reader with known length.
212    ///
213    /// Does not set filename or mime.
214    pub fn reader_with_length<T: Read + Send + 'static>(value: T, length: u64) -> Part {
215        Part::new(Body::sized(value, length))
216    }
217
218    /// Makes a file parameter.
219    ///
220    /// # Errors
221    ///
222    /// Errors when the file cannot be opened.
223    pub fn file<T: AsRef<Path>>(path: T) -> io::Result<Part> {
224        let path = path.as_ref();
225        let file_name = path
226            .file_name()
227            .map(|filename| filename.to_string_lossy().into_owned());
228        let ext = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
229        let mime = mime_guess::from_ext(ext).first_or_octet_stream();
230        let file = File::open(path)?;
231        let field = Part::new(Body::from(file)).mime(mime);
232
233        Ok(if let Some(file_name) = file_name {
234            field.file_name(file_name)
235        } else {
236            field
237        })
238    }
239
240    fn new(value: Body) -> Part {
241        Part {
242            meta: PartMetadata::new(),
243            value,
244        }
245    }
246
247    /// Tries to set the mime of this part.
248    pub fn mime_str(self, mime: &str) -> crate::Result<Part> {
249        Ok(self.mime(mime.parse().map_err(crate::error::builder)?))
250    }
251
252    // Re-export when mime 0.4 is available, with split MediaType/MediaRange.
253    fn mime(self, mime: Mime) -> Part {
254        self.with_inner(move |inner| inner.mime(mime))
255    }
256
257    /// Sets the filename, builder style.
258    pub fn file_name<T>(self, filename: T) -> Part
259    where
260        T: Into<Cow<'static, str>>,
261    {
262        self.with_inner(move |inner| inner.file_name(filename))
263    }
264
265    /// Sets custom headers for the part.
266    pub fn headers(self, headers: HeaderMap) -> Part {
267        self.with_inner(move |inner| inner.headers(headers))
268    }
269
270    fn with_inner<F>(self, func: F) -> Self
271    where
272        F: FnOnce(PartMetadata) -> PartMetadata,
273    {
274        Part {
275            meta: func(self.meta),
276            value: self.value,
277        }
278    }
279}
280
281impl fmt::Debug for Part {
282    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
283        let mut dbg = f.debug_struct("Part");
284        dbg.field("value", &self.value);
285        self.meta.fmt_fields(&mut dbg);
286        dbg.finish()
287    }
288}
289
290impl PartProps for Part {
291    fn value_len(&self) -> Option<u64> {
292        self.value.len()
293    }
294
295    fn metadata(&self) -> &PartMetadata {
296        &self.meta
297    }
298}
299
300pub(crate) struct Reader {
301    form: Form,
302    active_reader: Option<Box<dyn Read + Send>>,
303}
304
305impl fmt::Debug for Reader {
306    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
307        f.debug_struct("Reader").field("form", &self.form).finish()
308    }
309}
310
311impl Reader {
312    fn new(form: Form) -> Reader {
313        let mut reader = Reader {
314            form,
315            active_reader: None,
316        };
317        reader.next_reader();
318        reader
319    }
320
321    fn next_reader(&mut self) {
322        self.active_reader = if !self.form.inner.fields.is_empty() {
323            // We need to move out of the vector here because we are consuming the field's reader
324            let (name, field) = self.form.inner.fields.remove(0);
325            let boundary = Cursor::new(format!("--{}\r\n", self.form.boundary()));
326            let header = Cursor::new({
327                // Try to use cached headers created by compute_length
328                let mut h = if !self.form.inner.computed_headers.is_empty() {
329                    self.form.inner.computed_headers.remove(0)
330                } else {
331                    self.form
332                        .inner
333                        .percent_encoding
334                        .encode_headers(&name, field.metadata())
335                };
336                h.extend_from_slice(b"\r\n\r\n");
337                h
338            });
339            let reader = boundary
340                .chain(header)
341                .chain(field.value.into_reader())
342                .chain(Cursor::new("\r\n"));
343            // According to https://tools.ietf.org/html/rfc2046#section-5.1.1
344            // the very last field has a special boundary
345            if !self.form.inner.fields.is_empty() {
346                Some(Box::new(reader))
347            } else {
348                Some(Box::new(reader.chain(Cursor::new(format!(
349                    "--{}--\r\n",
350                    self.form.boundary()
351                )))))
352            }
353        } else {
354            None
355        }
356    }
357}
358
359impl Read for Reader {
360    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
361        let mut total_bytes_read = 0usize;
362        let mut last_read_bytes;
363        loop {
364            match self.active_reader {
365                Some(ref mut reader) => {
366                    last_read_bytes = reader.read(&mut buf[total_bytes_read..])?;
367                    total_bytes_read += last_read_bytes;
368                    if total_bytes_read == buf.len() {
369                        return Ok(total_bytes_read);
370                    }
371                }
372                None => return Ok(total_bytes_read),
373            };
374            if last_read_bytes == 0 && !buf.is_empty() {
375                self.next_reader();
376            }
377        }
378    }
379}
380
381#[cfg(test)]
382mod tests {
383    use super::*;
384
385    #[test]
386    fn form_empty() {
387        let mut output = Vec::new();
388        let mut form = Form::new();
389        let length = form.compute_length();
390        form.reader().read_to_end(&mut output).unwrap();
391        assert_eq!(output, b"");
392        assert_eq!(length.unwrap(), 0);
393    }
394
395    #[test]
396    fn read_to_end() {
397        let mut output = Vec::new();
398        let mut form = Form::new()
399            .part("reader1", Part::reader(std::io::empty()))
400            .part("key1", Part::text("value1"))
401            .part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
402            .part("reader2", Part::reader(std::io::empty()))
403            .part("key3", Part::text("value3").file_name("filename"));
404        form.inner.boundary = "boundary".to_string();
405        let length = form.compute_length();
406        let expected = "--boundary\r\n\
407             Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
408             \r\n\
409             --boundary\r\n\
410             Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
411             value1\r\n\
412             --boundary\r\n\
413             Content-Disposition: form-data; name=\"key2\"\r\n\
414             Content-Type: image/bmp\r\n\r\n\
415             value2\r\n\
416             --boundary\r\n\
417             Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
418             \r\n\
419             --boundary\r\n\
420             Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
421             value3\r\n--boundary--\r\n";
422        form.reader().read_to_end(&mut output).unwrap();
423        // These prints are for debug purposes in case the test fails
424        println!(
425            "START REAL\n{}\nEND REAL",
426            std::str::from_utf8(&output).unwrap()
427        );
428        println!("START EXPECTED\n{expected}\nEND EXPECTED");
429        assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
430        assert!(length.is_none());
431    }
432
433    #[test]
434    fn read_to_end_with_length() {
435        let mut output = Vec::new();
436        let mut form = Form::new()
437            .text("key1", "value1")
438            .part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
439            .part("key3", Part::text("value3").file_name("filename"));
440        form.inner.boundary = "boundary".to_string();
441        let length = form.compute_length();
442        let expected = "--boundary\r\n\
443             Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
444             value1\r\n\
445             --boundary\r\n\
446             Content-Disposition: form-data; name=\"key2\"\r\n\
447             Content-Type: image/bmp\r\n\r\n\
448             value2\r\n\
449             --boundary\r\n\
450             Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
451             value3\r\n--boundary--\r\n";
452        form.reader().read_to_end(&mut output).unwrap();
453        // These prints are for debug purposes in case the test fails
454        println!(
455            "START REAL\n{}\nEND REAL",
456            std::str::from_utf8(&output).unwrap()
457        );
458        println!("START EXPECTED\n{expected}\nEND EXPECTED");
459        assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
460        assert_eq!(length.unwrap(), expected.len() as u64);
461    }
462
463    #[test]
464    fn read_to_end_with_header() {
465        let mut output = Vec::new();
466        let mut part = Part::text("value2").mime(mime::IMAGE_BMP);
467        let mut headers = HeaderMap::new();
468        headers.insert("Hdr3", "/a/b/c".parse().unwrap());
469        part = part.headers(headers);
470        let mut form = Form::new().part("key2", part);
471        form.inner.boundary = "boundary".to_string();
472        let expected = "--boundary\r\n\
473                        Content-Disposition: form-data; name=\"key2\"\r\n\
474                        Content-Type: image/bmp\r\n\
475                        hdr3: /a/b/c\r\n\
476                        \r\n\
477                        value2\r\n\
478                        --boundary--\r\n";
479        form.reader().read_to_end(&mut output).unwrap();
480        // These prints are for debug purposes in case the test fails
481        println!(
482            "START REAL\n{}\nEND REAL",
483            std::str::from_utf8(&output).unwrap()
484        );
485        println!("START EXPECTED\n{expected}\nEND EXPECTED");
486        assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
487    }
488}