1 //! multipart/form-data
2 use std::borrow::Cow;
3 use std::fmt;
4 use std::pin::Pin;
5
6 use bytes::Bytes;
7 use http::HeaderMap;
8 use mime_guess::Mime;
9 use percent_encoding::{self, AsciiSet, NON_ALPHANUMERIC};
10
11 use futures_core::Stream;
12 use futures_util::{future, stream, StreamExt};
13
14 use super::Body;
15
16 /// An async multipart/form-data request.
17 pub struct Form {
18 inner: FormParts<Part>,
19 }
20
21 /// A field in a multipart form.
22 pub struct Part {
23 meta: PartMetadata,
24 value: Body,
25 body_length: Option<u64>,
26 }
27
28 pub(crate) struct FormParts<P> {
29 pub(crate) boundary: String,
30 pub(crate) computed_headers: Vec<Vec<u8>>,
31 pub(crate) fields: Vec<(Cow<'static, str>, P)>,
32 pub(crate) percent_encoding: PercentEncoding,
33 }
34
35 pub(crate) struct PartMetadata {
36 mime: Option<Mime>,
37 file_name: Option<Cow<'static, str>>,
38 pub(crate) headers: HeaderMap,
39 }
40
41 pub(crate) trait PartProps {
value_len(&self) -> Option<u64>42 fn value_len(&self) -> Option<u64>;
metadata(&self) -> &PartMetadata43 fn metadata(&self) -> &PartMetadata;
44 }
45
46 // ===== impl Form =====
47
48 impl Default for Form {
default() -> Self49 fn default() -> Self {
50 Self::new()
51 }
52 }
53
54 impl Form {
55 /// Creates a new async Form without any content.
new() -> Form56 pub fn new() -> Form {
57 Form {
58 inner: FormParts::new(),
59 }
60 }
61
62 /// Get the boundary that this form will use.
63 #[inline]
boundary(&self) -> &str64 pub fn boundary(&self) -> &str {
65 self.inner.boundary()
66 }
67
68 /// Add a data field with supplied name and value.
69 ///
70 /// # Examples
71 ///
72 /// ```
73 /// let form = reqwest::multipart::Form::new()
74 /// .text("username", "seanmonstar")
75 /// .text("password", "secret");
76 /// ```
text<T, U>(self, name: T, value: U) -> Form where T: Into<Cow<'static, str>>, U: Into<Cow<'static, str>>,77 pub fn text<T, U>(self, name: T, value: U) -> Form
78 where
79 T: Into<Cow<'static, str>>,
80 U: Into<Cow<'static, str>>,
81 {
82 self.part(name, Part::text(value))
83 }
84
85 /// Adds a customized Part.
part<T>(self, name: T, part: Part) -> Form where T: Into<Cow<'static, str>>,86 pub fn part<T>(self, name: T, part: Part) -> Form
87 where
88 T: Into<Cow<'static, str>>,
89 {
90 self.with_inner(move |inner| inner.part(name, part))
91 }
92
93 /// Configure this `Form` to percent-encode using the `path-segment` rules.
percent_encode_path_segment(self) -> Form94 pub fn percent_encode_path_segment(self) -> Form {
95 self.with_inner(|inner| inner.percent_encode_path_segment())
96 }
97
98 /// Configure this `Form` to percent-encode using the `attr-char` rules.
percent_encode_attr_chars(self) -> Form99 pub fn percent_encode_attr_chars(self) -> Form {
100 self.with_inner(|inner| inner.percent_encode_attr_chars())
101 }
102
103 /// Configure this `Form` to skip percent-encoding
percent_encode_noop(self) -> Form104 pub fn percent_encode_noop(self) -> Form {
105 self.with_inner(|inner| inner.percent_encode_noop())
106 }
107
108 /// Consume this instance and transform into an instance of Body for use in a request.
stream(mut self) -> Body109 pub(crate) fn stream(mut self) -> Body {
110 if self.inner.fields.is_empty() {
111 return Body::empty();
112 }
113
114 // create initial part to init reduce chain
115 let (name, part) = self.inner.fields.remove(0);
116 let start = Box::pin(self.part_stream(name, part))
117 as Pin<Box<dyn Stream<Item = crate::Result<Bytes>> + Send + Sync>>;
118
119 let fields = self.inner.take_fields();
120 // for each field, chain an additional stream
121 let stream = fields.into_iter().fold(start, |memo, (name, part)| {
122 let part_stream = self.part_stream(name, part);
123 Box::pin(memo.chain(part_stream))
124 as Pin<Box<dyn Stream<Item = crate::Result<Bytes>> + Send + Sync>>
125 });
126 // append special ending boundary
127 let last = stream::once(future::ready(Ok(
128 format!("--{}--\r\n", self.boundary()).into()
129 )));
130 Body::stream(stream.chain(last))
131 }
132
133 /// Generate a hyper::Body stream for a single Part instance of a Form request.
part_stream<T>( &mut self, name: T, part: Part, ) -> impl Stream<Item = Result<Bytes, crate::Error>> where T: Into<Cow<'static, str>>,134 pub(crate) fn part_stream<T>(
135 &mut self,
136 name: T,
137 part: Part,
138 ) -> impl Stream<Item = Result<Bytes, crate::Error>>
139 where
140 T: Into<Cow<'static, str>>,
141 {
142 // start with boundary
143 let boundary = stream::once(future::ready(Ok(
144 format!("--{}\r\n", self.boundary()).into()
145 )));
146 // append headers
147 let header = stream::once(future::ready(Ok({
148 let mut h = self
149 .inner
150 .percent_encoding
151 .encode_headers(&name.into(), &part.meta);
152 h.extend_from_slice(b"\r\n\r\n");
153 h.into()
154 })));
155 // then append form data followed by terminating CRLF
156 boundary
157 .chain(header)
158 .chain(part.value.into_stream())
159 .chain(stream::once(future::ready(Ok("\r\n".into()))))
160 }
161
compute_length(&mut self) -> Option<u64>162 pub(crate) fn compute_length(&mut self) -> Option<u64> {
163 self.inner.compute_length()
164 }
165
with_inner<F>(self, func: F) -> Self where F: FnOnce(FormParts<Part>) -> FormParts<Part>,166 fn with_inner<F>(self, func: F) -> Self
167 where
168 F: FnOnce(FormParts<Part>) -> FormParts<Part>,
169 {
170 Form {
171 inner: func(self.inner),
172 }
173 }
174 }
175
176 impl fmt::Debug for Form {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result177 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
178 self.inner.fmt_fields("Form", f)
179 }
180 }
181
182 // ===== impl Part =====
183
184 impl Part {
185 /// Makes a text parameter.
text<T>(value: T) -> Part where T: Into<Cow<'static, str>>,186 pub fn text<T>(value: T) -> Part
187 where
188 T: Into<Cow<'static, str>>,
189 {
190 let body = match value.into() {
191 Cow::Borrowed(slice) => Body::from(slice),
192 Cow::Owned(string) => Body::from(string),
193 };
194 Part::new(body, None)
195 }
196
197 /// Makes a new parameter from arbitrary bytes.
bytes<T>(value: T) -> Part where T: Into<Cow<'static, [u8]>>,198 pub fn bytes<T>(value: T) -> Part
199 where
200 T: Into<Cow<'static, [u8]>>,
201 {
202 let body = match value.into() {
203 Cow::Borrowed(slice) => Body::from(slice),
204 Cow::Owned(vec) => Body::from(vec),
205 };
206 Part::new(body, None)
207 }
208
209 /// Makes a new parameter from an arbitrary stream.
stream<T: Into<Body>>(value: T) -> Part210 pub fn stream<T: Into<Body>>(value: T) -> Part {
211 Part::new(value.into(), None)
212 }
213
214 /// Makes a new parameter from an arbitrary stream with a known length. This is particularly
215 /// useful when adding something like file contents as a stream, where you can know the content
216 /// length beforehand.
stream_with_length<T: Into<Body>>(value: T, length: u64) -> Part217 pub fn stream_with_length<T: Into<Body>>(value: T, length: u64) -> Part {
218 Part::new(value.into(), Some(length))
219 }
220
new(value: Body, body_length: Option<u64>) -> Part221 fn new(value: Body, body_length: Option<u64>) -> Part {
222 Part {
223 meta: PartMetadata::new(),
224 value,
225 body_length,
226 }
227 }
228
229 /// Tries to set the mime of this part.
mime_str(self, mime: &str) -> crate::Result<Part>230 pub fn mime_str(self, mime: &str) -> crate::Result<Part> {
231 Ok(self.mime(mime.parse().map_err(crate::error::builder)?))
232 }
233
234 // Re-export when mime 0.4 is available, with split MediaType/MediaRange.
mime(self, mime: Mime) -> Part235 fn mime(self, mime: Mime) -> Part {
236 self.with_inner(move |inner| inner.mime(mime))
237 }
238
239 /// Sets the filename, builder style.
file_name<T>(self, filename: T) -> Part where T: Into<Cow<'static, str>>,240 pub fn file_name<T>(self, filename: T) -> Part
241 where
242 T: Into<Cow<'static, str>>,
243 {
244 self.with_inner(move |inner| inner.file_name(filename))
245 }
246
with_inner<F>(self, func: F) -> Self where F: FnOnce(PartMetadata) -> PartMetadata,247 fn with_inner<F>(self, func: F) -> Self
248 where
249 F: FnOnce(PartMetadata) -> PartMetadata,
250 {
251 Part {
252 meta: func(self.meta),
253 ..self
254 }
255 }
256 }
257
258 impl fmt::Debug for Part {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result259 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
260 let mut dbg = f.debug_struct("Part");
261 dbg.field("value", &self.value);
262 self.meta.fmt_fields(&mut dbg);
263 dbg.finish()
264 }
265 }
266
267 impl PartProps for Part {
value_len(&self) -> Option<u64>268 fn value_len(&self) -> Option<u64> {
269 if self.body_length.is_some() {
270 self.body_length
271 } else {
272 self.value.content_length()
273 }
274 }
275
metadata(&self) -> &PartMetadata276 fn metadata(&self) -> &PartMetadata {
277 &self.meta
278 }
279 }
280
281 // ===== impl FormParts =====
282
283 impl<P: PartProps> FormParts<P> {
new() -> Self284 pub(crate) fn new() -> Self {
285 FormParts {
286 boundary: gen_boundary(),
287 computed_headers: Vec::new(),
288 fields: Vec::new(),
289 percent_encoding: PercentEncoding::PathSegment,
290 }
291 }
292
boundary(&self) -> &str293 pub(crate) fn boundary(&self) -> &str {
294 &self.boundary
295 }
296
297 /// Adds a customized Part.
part<T>(mut self, name: T, part: P) -> Self where T: Into<Cow<'static, str>>,298 pub(crate) fn part<T>(mut self, name: T, part: P) -> Self
299 where
300 T: Into<Cow<'static, str>>,
301 {
302 self.fields.push((name.into(), part));
303 self
304 }
305
306 /// Configure this `Form` to percent-encode using the `path-segment` rules.
percent_encode_path_segment(mut self) -> Self307 pub(crate) fn percent_encode_path_segment(mut self) -> Self {
308 self.percent_encoding = PercentEncoding::PathSegment;
309 self
310 }
311
312 /// Configure this `Form` to percent-encode using the `attr-char` rules.
percent_encode_attr_chars(mut self) -> Self313 pub(crate) fn percent_encode_attr_chars(mut self) -> Self {
314 self.percent_encoding = PercentEncoding::AttrChar;
315 self
316 }
317
318 /// Configure this `Form` to skip percent-encoding
percent_encode_noop(mut self) -> Self319 pub(crate) fn percent_encode_noop(mut self) -> Self {
320 self.percent_encoding = PercentEncoding::NoOp;
321 self
322 }
323
324 // If predictable, computes the length the request will have
325 // The length should be preditable if only String and file fields have been added,
326 // but not if a generic reader has been added;
compute_length(&mut self) -> Option<u64>327 pub(crate) fn compute_length(&mut self) -> Option<u64> {
328 let mut length = 0u64;
329 for &(ref name, ref field) in self.fields.iter() {
330 match field.value_len() {
331 Some(value_length) => {
332 // We are constructing the header just to get its length. To not have to
333 // construct it again when the request is sent we cache these headers.
334 let header = self.percent_encoding.encode_headers(name, field.metadata());
335 let header_length = header.len();
336 self.computed_headers.push(header);
337 // The additions mimic the format string out of which the field is constructed
338 // in Reader. Not the cleanest solution because if that format string is
339 // ever changed then this formula needs to be changed too which is not an
340 // obvious dependency in the code.
341 length += 2
342 + self.boundary().len() as u64
343 + 2
344 + header_length as u64
345 + 4
346 + value_length
347 + 2
348 }
349 _ => return None,
350 }
351 }
352 // If there is a at least one field there is a special boundary for the very last field.
353 if !self.fields.is_empty() {
354 length += 2 + self.boundary().len() as u64 + 4
355 }
356 Some(length)
357 }
358
359 /// Take the fields vector of this instance, replacing with an empty vector.
take_fields(&mut self) -> Vec<(Cow<'static, str>, P)>360 fn take_fields(&mut self) -> Vec<(Cow<'static, str>, P)> {
361 std::mem::replace(&mut self.fields, Vec::new())
362 }
363 }
364
365 impl<P: fmt::Debug> FormParts<P> {
fmt_fields(&self, ty_name: &'static str, f: &mut fmt::Formatter) -> fmt::Result366 pub(crate) fn fmt_fields(&self, ty_name: &'static str, f: &mut fmt::Formatter) -> fmt::Result {
367 f.debug_struct(ty_name)
368 .field("boundary", &self.boundary)
369 .field("parts", &self.fields)
370 .finish()
371 }
372 }
373
374 // ===== impl PartMetadata =====
375
376 impl PartMetadata {
new() -> Self377 pub(crate) fn new() -> Self {
378 PartMetadata {
379 mime: None,
380 file_name: None,
381 headers: HeaderMap::default(),
382 }
383 }
384
mime(mut self, mime: Mime) -> Self385 pub(crate) fn mime(mut self, mime: Mime) -> Self {
386 self.mime = Some(mime);
387 self
388 }
389
file_name<T>(mut self, filename: T) -> Self where T: Into<Cow<'static, str>>,390 pub(crate) fn file_name<T>(mut self, filename: T) -> Self
391 where
392 T: Into<Cow<'static, str>>,
393 {
394 self.file_name = Some(filename.into());
395 self
396 }
397 }
398
399 impl PartMetadata {
fmt_fields<'f, 'fa, 'fb>( &self, debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>, ) -> &'f mut fmt::DebugStruct<'fa, 'fb>400 pub(crate) fn fmt_fields<'f, 'fa, 'fb>(
401 &self,
402 debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>,
403 ) -> &'f mut fmt::DebugStruct<'fa, 'fb> {
404 debug_struct
405 .field("mime", &self.mime)
406 .field("file_name", &self.file_name)
407 .field("headers", &self.headers)
408 }
409 }
410
411 // https://url.spec.whatwg.org/#fragment-percent-encode-set
412 const FRAGMENT_ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS
413 .add(b' ')
414 .add(b'"')
415 .add(b'<')
416 .add(b'>')
417 .add(b'`');
418
419 // https://url.spec.whatwg.org/#path-percent-encode-set
420 const PATH_ENCODE_SET: &AsciiSet = &FRAGMENT_ENCODE_SET.add(b'#').add(b'?').add(b'{').add(b'}');
421
422 const PATH_SEGMENT_ENCODE_SET: &AsciiSet = &PATH_ENCODE_SET.add(b'/').add(b'%');
423
424 // https://tools.ietf.org/html/rfc8187#section-3.2.1
425 const ATTR_CHAR_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC
426 .remove(b'!')
427 .remove(b'#')
428 .remove(b'$')
429 .remove(b'&')
430 .remove(b'+')
431 .remove(b'-')
432 .remove(b'.')
433 .remove(b'^')
434 .remove(b'_')
435 .remove(b'`')
436 .remove(b'|')
437 .remove(b'~');
438
439 pub(crate) enum PercentEncoding {
440 PathSegment,
441 AttrChar,
442 NoOp,
443 }
444
445 impl PercentEncoding {
encode_headers(&self, name: &str, field: &PartMetadata) -> Vec<u8>446 pub(crate) fn encode_headers(&self, name: &str, field: &PartMetadata) -> Vec<u8> {
447 let s = format!(
448 "Content-Disposition: form-data; {}{}{}",
449 self.format_parameter("name", name),
450 match field.file_name {
451 Some(ref file_name) => format!("; {}", self.format_filename(file_name)),
452 None => String::new(),
453 },
454 match field.mime {
455 Some(ref mime) => format!("\r\nContent-Type: {}", mime),
456 None => "".to_string(),
457 },
458 );
459 field
460 .headers
461 .iter()
462 .fold(s.into_bytes(), |mut header, (k, v)| {
463 header.extend_from_slice(b"\r\n");
464 header.extend_from_slice(k.as_str().as_bytes());
465 header.extend_from_slice(b": ");
466 header.extend_from_slice(v.as_bytes());
467 header
468 })
469 }
470
471 // According to RFC7578 Section 4.2, `filename*=` syntax is invalid.
472 // See https://github.com/seanmonstar/reqwest/issues/419.
format_filename(&self, filename: &str) -> String473 fn format_filename(&self, filename: &str) -> String {
474 let legal_filename = filename
475 .replace("\\", "\\\\")
476 .replace("\"", "\\\"")
477 .replace("\r", "\\\r")
478 .replace("\n", "\\\n");
479 format!("filename=\"{}\"", legal_filename)
480 }
481
format_parameter(&self, name: &str, value: &str) -> String482 fn format_parameter(&self, name: &str, value: &str) -> String {
483 let legal_value = match *self {
484 PercentEncoding::PathSegment => {
485 percent_encoding::utf8_percent_encode(value, PATH_SEGMENT_ENCODE_SET).to_string()
486 }
487 PercentEncoding::AttrChar => {
488 percent_encoding::utf8_percent_encode(value, ATTR_CHAR_ENCODE_SET).to_string()
489 }
490 PercentEncoding::NoOp => value.to_string(),
491 };
492 if value.len() == legal_value.len() {
493 // nothing has been percent encoded
494 format!("{}=\"{}\"", name, value)
495 } else {
496 // something has been percent encoded
497 format!("{}*=utf-8''{}", name, legal_value)
498 }
499 }
500 }
501
gen_boundary() -> String502 fn gen_boundary() -> String {
503 use crate::util::fast_random as random;
504
505 let a = random();
506 let b = random();
507 let c = random();
508 let d = random();
509
510 format!("{:016x}-{:016x}-{:016x}-{:016x}", a, b, c, d)
511 }
512
513 #[cfg(test)]
514 mod tests {
515 use super::*;
516 use futures_util::TryStreamExt;
517 use futures_util::{future, stream};
518 use tokio::{self, runtime};
519
520 #[test]
form_empty()521 fn form_empty() {
522 let form = Form::new();
523
524 let rt = runtime::Builder::new_current_thread()
525 .enable_all()
526 .build()
527 .expect("new rt");
528 let body = form.stream().into_stream();
529 let s = body.map_ok(|try_c| try_c.to_vec()).try_concat();
530
531 let out = rt.block_on(s);
532 assert!(out.unwrap().is_empty());
533 }
534
535 #[test]
stream_to_end()536 fn stream_to_end() {
537 let mut form = Form::new()
538 .part(
539 "reader1",
540 Part::stream(Body::stream(stream::once(future::ready::<
541 Result<String, crate::Error>,
542 >(Ok(
543 "part1".to_owned()
544 ))))),
545 )
546 .part("key1", Part::text("value1"))
547 .part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
548 .part(
549 "reader2",
550 Part::stream(Body::stream(stream::once(future::ready::<
551 Result<String, crate::Error>,
552 >(Ok(
553 "part2".to_owned()
554 ))))),
555 )
556 .part("key3", Part::text("value3").file_name("filename"));
557 form.inner.boundary = "boundary".to_string();
558 let expected = "--boundary\r\n\
559 Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
560 part1\r\n\
561 --boundary\r\n\
562 Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
563 value1\r\n\
564 --boundary\r\n\
565 Content-Disposition: form-data; name=\"key2\"\r\n\
566 Content-Type: image/bmp\r\n\r\n\
567 value2\r\n\
568 --boundary\r\n\
569 Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
570 part2\r\n\
571 --boundary\r\n\
572 Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
573 value3\r\n--boundary--\r\n";
574 let rt = runtime::Builder::new_current_thread()
575 .enable_all()
576 .build()
577 .expect("new rt");
578 let body = form.stream().into_stream();
579 let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat();
580
581 let out = rt.block_on(s).unwrap();
582 // These prints are for debug purposes in case the test fails
583 println!(
584 "START REAL\n{}\nEND REAL",
585 std::str::from_utf8(&out).unwrap()
586 );
587 println!("START EXPECTED\n{}\nEND EXPECTED", expected);
588 assert_eq!(std::str::from_utf8(&out).unwrap(), expected);
589 }
590
591 #[test]
stream_to_end_with_header()592 fn stream_to_end_with_header() {
593 let mut part = Part::text("value2").mime(mime::IMAGE_BMP);
594 part.meta.headers.insert("Hdr3", "/a/b/c".parse().unwrap());
595 let mut form = Form::new().part("key2", part);
596 form.inner.boundary = "boundary".to_string();
597 let expected = "--boundary\r\n\
598 Content-Disposition: form-data; name=\"key2\"\r\n\
599 Content-Type: image/bmp\r\n\
600 hdr3: /a/b/c\r\n\
601 \r\n\
602 value2\r\n\
603 --boundary--\r\n";
604 let rt = runtime::Builder::new_current_thread()
605 .enable_all()
606 .build()
607 .expect("new rt");
608 let body = form.stream().into_stream();
609 let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat();
610
611 let out = rt.block_on(s).unwrap();
612 // These prints are for debug purposes in case the test fails
613 println!(
614 "START REAL\n{}\nEND REAL",
615 std::str::from_utf8(&out).unwrap()
616 );
617 println!("START EXPECTED\n{}\nEND EXPECTED", expected);
618 assert_eq!(std::str::from_utf8(&out).unwrap(), expected);
619 }
620
621 #[test]
correct_content_length()622 fn correct_content_length() {
623 // Setup an arbitrary data stream
624 let stream_data = b"just some stream data";
625 let stream_len = stream_data.len();
626 let stream_data = stream_data
627 .chunks(3)
628 .map(|c| Ok::<_, std::io::Error>(Bytes::from(c)));
629 let the_stream = futures_util::stream::iter(stream_data);
630
631 let bytes_data = b"some bytes data".to_vec();
632 let bytes_len = bytes_data.len();
633
634 let stream_part = Part::stream_with_length(Body::stream(the_stream), stream_len as u64);
635 let body_part = Part::bytes(bytes_data);
636
637 // A simple check to make sure we get the configured body length
638 assert_eq!(stream_part.value_len().unwrap(), stream_len as u64);
639
640 // Make sure it delegates to the underlying body if length is not specified
641 assert_eq!(body_part.value_len().unwrap(), bytes_len as u64);
642 }
643
644 #[test]
header_percent_encoding()645 fn header_percent_encoding() {
646 let name = "start%'\"\r\nßend";
647 let field = Part::text("");
648
649 assert_eq!(
650 PercentEncoding::PathSegment.encode_headers(name, &field.meta),
651 &b"Content-Disposition: form-data; name*=utf-8''start%25'%22%0D%0A%C3%9Fend"[..]
652 );
653
654 assert_eq!(
655 PercentEncoding::AttrChar.encode_headers(name, &field.meta),
656 &b"Content-Disposition: form-data; name*=utf-8''start%25%27%22%0D%0A%C3%9Fend"[..]
657 );
658 }
659 }
660