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