1use std::borrow::Cow;
3use std::fmt;
4use std::pin::Pin;
5
6#[cfg(feature = "stream")]
7use std::io;
8#[cfg(feature = "stream")]
9use std::path::Path;
10
11use bytes::Bytes;
12use mime_guess::Mime;
13use percent_encoding::{self, AsciiSet, NON_ALPHANUMERIC};
14#[cfg(feature = "stream")]
15use tokio::fs::File;
16
17use futures_core::Stream;
18use futures_util::{future, stream, StreamExt};
19use http_body_util::BodyExt;
20
21use super::Body;
22use crate::header::HeaderMap;
23
24pub struct Form {
26 inner: FormParts<Part>,
27}
28
29pub struct Part {
31 meta: PartMetadata,
32 value: Body,
33 body_length: Option<u64>,
34}
35
36pub(crate) struct FormParts<P> {
37 pub(crate) boundary: String,
38 pub(crate) computed_headers: Vec<Vec<u8>>,
39 pub(crate) fields: Vec<(Cow<'static, str>, P)>,
40 pub(crate) percent_encoding: PercentEncoding,
41}
42
43pub(crate) struct PartMetadata {
44 mime: Option<Mime>,
45 file_name: Option<Cow<'static, str>>,
46 pub(crate) headers: HeaderMap,
47}
48
49pub(crate) trait PartProps {
50 fn value_len(&self) -> Option<u64>;
51 fn metadata(&self) -> &PartMetadata;
52}
53
54impl Default for Form {
57 fn default() -> Self {
58 Self::new()
59 }
60}
61
62impl Form {
63 pub fn new() -> Form {
65 Form {
66 inner: FormParts::new(),
67 }
68 }
69
70 #[inline]
72 pub fn boundary(&self) -> &str {
73 self.inner.boundary()
74 }
75
76 pub fn text<T, U>(self, name: T, value: U) -> Form
86 where
87 T: Into<Cow<'static, str>>,
88 U: Into<Cow<'static, str>>,
89 {
90 self.part(name, Part::text(value))
91 }
92
93 #[cfg(feature = "stream")]
111 #[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
112 pub async fn file<T, U>(self, name: T, path: U) -> io::Result<Form>
113 where
114 T: Into<Cow<'static, str>>,
115 U: AsRef<Path>,
116 {
117 Ok(self.part(name, Part::file(path).await?))
118 }
119
120 pub fn part<T>(self, name: T, part: Part) -> Form
122 where
123 T: Into<Cow<'static, str>>,
124 {
125 self.with_inner(move |inner| inner.part(name, part))
126 }
127
128 pub fn percent_encode_path_segment(self) -> Form {
130 self.with_inner(|inner| inner.percent_encode_path_segment())
131 }
132
133 pub fn percent_encode_attr_chars(self) -> Form {
135 self.with_inner(|inner| inner.percent_encode_attr_chars())
136 }
137
138 pub fn percent_encode_noop(self) -> Form {
140 self.with_inner(|inner| inner.percent_encode_noop())
141 }
142
143 pub(crate) fn stream(self) -> Body {
145 if self.inner.fields.is_empty() {
146 return Body::empty();
147 }
148
149 Body::stream(self.into_stream())
150 }
151
152 pub fn into_stream(mut self) -> impl Stream<Item = Result<Bytes, crate::Error>> + Send + Sync {
154 if self.inner.fields.is_empty() {
155 let empty_stream: Pin<
156 Box<dyn Stream<Item = Result<Bytes, crate::Error>> + Send + Sync>,
157 > = Box::pin(futures_util::stream::empty());
158 return empty_stream;
159 }
160
161 let (name, part) = self.inner.fields.remove(0);
163 let start = Box::pin(self.part_stream(name, part))
164 as Pin<Box<dyn Stream<Item = crate::Result<Bytes>> + Send + Sync>>;
165
166 let fields = self.inner.take_fields();
167 let stream = fields.into_iter().fold(start, |memo, (name, part)| {
169 let part_stream = self.part_stream(name, part);
170 Box::pin(memo.chain(part_stream))
171 as Pin<Box<dyn Stream<Item = crate::Result<Bytes>> + Send + Sync>>
172 });
173 let last = stream::once(future::ready(Ok(
175 format!("--{}--\r\n", self.boundary()).into()
176 )));
177 Box::pin(stream.chain(last))
178 }
179
180 pub(crate) fn part_stream<T>(
182 &mut self,
183 name: T,
184 part: Part,
185 ) -> impl Stream<Item = Result<Bytes, crate::Error>>
186 where
187 T: Into<Cow<'static, str>>,
188 {
189 let boundary = stream::once(future::ready(Ok(
191 format!("--{}\r\n", self.boundary()).into()
192 )));
193 let header = stream::once(future::ready(Ok({
195 let mut h = self
196 .inner
197 .percent_encoding
198 .encode_headers(&name.into(), &part.meta);
199 h.extend_from_slice(b"\r\n\r\n");
200 h.into()
201 })));
202 boundary
204 .chain(header)
205 .chain(part.value.into_data_stream())
206 .chain(stream::once(future::ready(Ok("\r\n".into()))))
207 }
208
209 pub(crate) fn compute_length(&mut self) -> Option<u64> {
210 self.inner.compute_length()
211 }
212
213 fn with_inner<F>(self, func: F) -> Self
214 where
215 F: FnOnce(FormParts<Part>) -> FormParts<Part>,
216 {
217 Form {
218 inner: func(self.inner),
219 }
220 }
221}
222
223impl fmt::Debug for Form {
224 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
225 self.inner.fmt_fields("Form", f)
226 }
227}
228
229impl Part {
232 pub fn text<T>(value: T) -> Part
234 where
235 T: Into<Cow<'static, str>>,
236 {
237 let body = match value.into() {
238 Cow::Borrowed(slice) => Body::from(slice),
239 Cow::Owned(string) => Body::from(string),
240 };
241 Part::new(body, None)
242 }
243
244 pub fn bytes<T>(value: T) -> Part
246 where
247 T: Into<Cow<'static, [u8]>>,
248 {
249 let body = match value.into() {
250 Cow::Borrowed(slice) => Body::from(slice),
251 Cow::Owned(vec) => Body::from(vec),
252 };
253 Part::new(body, None)
254 }
255
256 pub fn stream<T: Into<Body>>(value: T) -> Part {
258 Part::new(value.into(), None)
259 }
260
261 pub fn stream_with_length<T: Into<Body>>(value: T, length: u64) -> Part {
265 Part::new(value.into(), Some(length))
266 }
267
268 #[cfg(feature = "stream")]
274 #[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
275 pub async fn file<T: AsRef<Path>>(path: T) -> io::Result<Part> {
276 let path = path.as_ref();
277 let file_name = path
278 .file_name()
279 .map(|filename| filename.to_string_lossy().into_owned());
280 let ext = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
281 let mime = mime_guess::from_ext(ext).first_or_octet_stream();
282 let file = File::open(path).await?;
283 let len = file.metadata().await.map(|m| m.len()).ok();
284 let field = match len {
285 Some(len) => Part::stream_with_length(file, len),
286 None => Part::stream(file),
287 }
288 .mime(mime);
289
290 Ok(if let Some(file_name) = file_name {
291 field.file_name(file_name)
292 } else {
293 field
294 })
295 }
296
297 fn new(value: Body, body_length: Option<u64>) -> Part {
298 Part {
299 meta: PartMetadata::new(),
300 value,
301 body_length,
302 }
303 }
304
305 pub fn mime_str(self, mime: &str) -> crate::Result<Part> {
307 Ok(self.mime(mime.parse().map_err(crate::error::builder)?))
308 }
309
310 fn mime(self, mime: Mime) -> Part {
312 self.with_inner(move |inner| inner.mime(mime))
313 }
314
315 pub fn file_name<T>(self, filename: T) -> Part
317 where
318 T: Into<Cow<'static, str>>,
319 {
320 self.with_inner(move |inner| inner.file_name(filename))
321 }
322
323 pub fn headers(self, headers: HeaderMap) -> Part {
325 self.with_inner(move |inner| inner.headers(headers))
326 }
327
328 fn with_inner<F>(self, func: F) -> Self
329 where
330 F: FnOnce(PartMetadata) -> PartMetadata,
331 {
332 Part {
333 meta: func(self.meta),
334 ..self
335 }
336 }
337}
338
339impl fmt::Debug for Part {
340 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
341 let mut dbg = f.debug_struct("Part");
342 dbg.field("value", &self.value);
343 self.meta.fmt_fields(&mut dbg);
344 dbg.finish()
345 }
346}
347
348impl PartProps for Part {
349 fn value_len(&self) -> Option<u64> {
350 if self.body_length.is_some() {
351 self.body_length
352 } else {
353 self.value.content_length()
354 }
355 }
356
357 fn metadata(&self) -> &PartMetadata {
358 &self.meta
359 }
360}
361
362impl<P: PartProps> FormParts<P> {
365 pub(crate) fn new() -> Self {
366 FormParts {
367 boundary: gen_boundary(),
368 computed_headers: Vec::new(),
369 fields: Vec::new(),
370 percent_encoding: PercentEncoding::PathSegment,
371 }
372 }
373
374 pub(crate) fn boundary(&self) -> &str {
375 &self.boundary
376 }
377
378 pub(crate) fn part<T>(mut self, name: T, part: P) -> Self
380 where
381 T: Into<Cow<'static, str>>,
382 {
383 self.fields.push((name.into(), part));
384 self
385 }
386
387 pub(crate) fn percent_encode_path_segment(mut self) -> Self {
389 self.percent_encoding = PercentEncoding::PathSegment;
390 self
391 }
392
393 pub(crate) fn percent_encode_attr_chars(mut self) -> Self {
395 self.percent_encoding = PercentEncoding::AttrChar;
396 self
397 }
398
399 pub(crate) fn percent_encode_noop(mut self) -> Self {
401 self.percent_encoding = PercentEncoding::NoOp;
402 self
403 }
404
405 pub(crate) fn compute_length(&mut self) -> Option<u64> {
409 let mut length = 0u64;
410 for &(ref name, ref field) in self.fields.iter() {
411 match field.value_len() {
412 Some(value_length) => {
413 let header = self.percent_encoding.encode_headers(name, field.metadata());
416 let header_length = header.len();
417 self.computed_headers.push(header);
418 length += 2
423 + self.boundary().len() as u64
424 + 2
425 + header_length as u64
426 + 4
427 + value_length
428 + 2
429 }
430 _ => return None,
431 }
432 }
433 if !self.fields.is_empty() {
435 length += 2 + self.boundary().len() as u64 + 4
436 }
437 Some(length)
438 }
439
440 fn take_fields(&mut self) -> Vec<(Cow<'static, str>, P)> {
442 std::mem::replace(&mut self.fields, Vec::new())
443 }
444}
445
446impl<P: fmt::Debug> FormParts<P> {
447 pub(crate) fn fmt_fields(&self, ty_name: &'static str, f: &mut fmt::Formatter) -> fmt::Result {
448 f.debug_struct(ty_name)
449 .field("boundary", &self.boundary)
450 .field("parts", &self.fields)
451 .finish()
452 }
453}
454
455impl PartMetadata {
458 pub(crate) fn new() -> Self {
459 PartMetadata {
460 mime: None,
461 file_name: None,
462 headers: HeaderMap::default(),
463 }
464 }
465
466 pub(crate) fn mime(mut self, mime: Mime) -> Self {
467 self.mime = Some(mime);
468 self
469 }
470
471 pub(crate) fn file_name<T>(mut self, filename: T) -> Self
472 where
473 T: Into<Cow<'static, str>>,
474 {
475 self.file_name = Some(filename.into());
476 self
477 }
478
479 pub(crate) fn headers<T>(mut self, headers: T) -> Self
480 where
481 T: Into<HeaderMap>,
482 {
483 self.headers = headers.into();
484 self
485 }
486}
487
488impl PartMetadata {
489 pub(crate) fn fmt_fields<'f, 'fa, 'fb>(
490 &self,
491 debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>,
492 ) -> &'f mut fmt::DebugStruct<'fa, 'fb> {
493 debug_struct
494 .field("mime", &self.mime)
495 .field("file_name", &self.file_name)
496 .field("headers", &self.headers)
497 }
498}
499
500const FRAGMENT_ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS
502 .add(b' ')
503 .add(b'"')
504 .add(b'<')
505 .add(b'>')
506 .add(b'`');
507
508const PATH_ENCODE_SET: &AsciiSet = &FRAGMENT_ENCODE_SET.add(b'#').add(b'?').add(b'{').add(b'}');
510
511const PATH_SEGMENT_ENCODE_SET: &AsciiSet = &PATH_ENCODE_SET.add(b'/').add(b'%');
512
513const ATTR_CHAR_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC
515 .remove(b'!')
516 .remove(b'#')
517 .remove(b'$')
518 .remove(b'&')
519 .remove(b'+')
520 .remove(b'-')
521 .remove(b'.')
522 .remove(b'^')
523 .remove(b'_')
524 .remove(b'`')
525 .remove(b'|')
526 .remove(b'~');
527
528pub(crate) enum PercentEncoding {
529 PathSegment,
530 AttrChar,
531 NoOp,
532}
533
534impl PercentEncoding {
535 pub(crate) fn encode_headers(&self, name: &str, field: &PartMetadata) -> Vec<u8> {
536 let mut buf = Vec::new();
537 buf.extend_from_slice(b"Content-Disposition: form-data; ");
538
539 match self.percent_encode(name) {
540 Cow::Borrowed(value) => {
541 buf.extend_from_slice(b"name=\"");
543 buf.extend_from_slice(value.as_bytes());
544 buf.extend_from_slice(b"\"");
545 }
546 Cow::Owned(value) => {
547 buf.extend_from_slice(b"name*=utf-8''");
549 buf.extend_from_slice(value.as_bytes());
550 }
551 }
552
553 if let Some(filename) = &field.file_name {
556 buf.extend_from_slice(b"; filename=\"");
557 let legal_filename = filename
558 .replace('\\', "\\\\")
559 .replace('"', "\\\"")
560 .replace('\r', "\\\r")
561 .replace('\n', "\\\n");
562 buf.extend_from_slice(legal_filename.as_bytes());
563 buf.extend_from_slice(b"\"");
564 }
565
566 if let Some(mime) = &field.mime {
567 buf.extend_from_slice(b"\r\nContent-Type: ");
568 buf.extend_from_slice(mime.as_ref().as_bytes());
569 }
570
571 for (k, v) in field.headers.iter() {
572 buf.extend_from_slice(b"\r\n");
573 buf.extend_from_slice(k.as_str().as_bytes());
574 buf.extend_from_slice(b": ");
575 buf.extend_from_slice(v.as_bytes());
576 }
577 buf
578 }
579
580 fn percent_encode<'a>(&self, value: &'a str) -> Cow<'a, str> {
581 use percent_encoding::utf8_percent_encode as percent_encode;
582
583 match self {
584 Self::PathSegment => percent_encode(value, PATH_SEGMENT_ENCODE_SET).into(),
585 Self::AttrChar => percent_encode(value, ATTR_CHAR_ENCODE_SET).into(),
586 Self::NoOp => value.into(),
587 }
588 }
589}
590
591fn gen_boundary() -> String {
592 use crate::util::fast_random as random;
593
594 let a = random();
595 let b = random();
596 let c = random();
597 let d = random();
598
599 format!("{a:016x}-{b:016x}-{c:016x}-{d:016x}")
600}
601
602#[cfg(test)]
603mod tests {
604 use super::*;
605 use futures_util::stream;
606 use futures_util::TryStreamExt;
607 use std::future;
608 use tokio::{self, runtime};
609
610 #[test]
611 fn form_empty() {
612 let form = Form::new();
613
614 let rt = runtime::Builder::new_current_thread()
615 .enable_all()
616 .build()
617 .expect("new rt");
618 let body = form.stream().into_data_stream();
619 let s = body.map_ok(|try_c| try_c.to_vec()).try_concat();
620
621 let out = rt.block_on(s);
622 assert!(out.unwrap().is_empty());
623 }
624
625 #[test]
626 fn stream_to_end() {
627 let mut form = Form::new()
628 .part(
629 "reader1",
630 Part::stream(Body::stream(stream::once(future::ready::<
631 Result<String, crate::Error>,
632 >(Ok(
633 "part1".to_owned()
634 ))))),
635 )
636 .part("key1", Part::text("value1"))
637 .part(
638 "key2",
639 Part::text("value2").mime(mime_guess::mime::IMAGE_BMP),
640 )
641 .part(
642 "reader2",
643 Part::stream(Body::stream(stream::once(future::ready::<
644 Result<String, crate::Error>,
645 >(Ok(
646 "part2".to_owned()
647 ))))),
648 )
649 .part("key3", Part::text("value3").file_name("filename"));
650 form.inner.boundary = "boundary".to_string();
651 let expected = "--boundary\r\n\
652 Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
653 part1\r\n\
654 --boundary\r\n\
655 Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
656 value1\r\n\
657 --boundary\r\n\
658 Content-Disposition: form-data; name=\"key2\"\r\n\
659 Content-Type: image/bmp\r\n\r\n\
660 value2\r\n\
661 --boundary\r\n\
662 Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
663 part2\r\n\
664 --boundary\r\n\
665 Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
666 value3\r\n--boundary--\r\n";
667 let rt = runtime::Builder::new_current_thread()
668 .enable_all()
669 .build()
670 .expect("new rt");
671 let body = form.stream().into_data_stream();
672 let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat();
673
674 let out = rt.block_on(s).unwrap();
675 println!(
677 "START REAL\n{}\nEND REAL",
678 std::str::from_utf8(&out).unwrap()
679 );
680 println!("START EXPECTED\n{expected}\nEND EXPECTED");
681 assert_eq!(std::str::from_utf8(&out).unwrap(), expected);
682 }
683
684 #[test]
685 fn stream_to_end_with_header() {
686 let mut part = Part::text("value2").mime(mime_guess::mime::IMAGE_BMP);
687 let mut headers = HeaderMap::new();
688 headers.insert("Hdr3", "/a/b/c".parse().unwrap());
689 part = part.headers(headers);
690 let mut form = Form::new().part("key2", part);
691 form.inner.boundary = "boundary".to_string();
692 let expected = "--boundary\r\n\
693 Content-Disposition: form-data; name=\"key2\"\r\n\
694 Content-Type: image/bmp\r\n\
695 hdr3: /a/b/c\r\n\
696 \r\n\
697 value2\r\n\
698 --boundary--\r\n";
699 let rt = runtime::Builder::new_current_thread()
700 .enable_all()
701 .build()
702 .expect("new rt");
703 let body = form.stream().into_data_stream();
704 let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat();
705
706 let out = rt.block_on(s).unwrap();
707 println!(
709 "START REAL\n{}\nEND REAL",
710 std::str::from_utf8(&out).unwrap()
711 );
712 println!("START EXPECTED\n{expected}\nEND EXPECTED");
713 assert_eq!(std::str::from_utf8(&out).unwrap(), expected);
714 }
715
716 #[test]
717 fn correct_content_length() {
718 let stream_data = b"just some stream data";
720 let stream_len = stream_data.len();
721 let stream_data = stream_data
722 .chunks(3)
723 .map(|c| Ok::<_, std::io::Error>(Bytes::from(c)));
724 let the_stream = futures_util::stream::iter(stream_data);
725
726 let bytes_data = b"some bytes data".to_vec();
727 let bytes_len = bytes_data.len();
728
729 let stream_part = Part::stream_with_length(Body::stream(the_stream), stream_len as u64);
730 let body_part = Part::bytes(bytes_data);
731
732 assert_eq!(stream_part.value_len().unwrap(), stream_len as u64);
734
735 assert_eq!(body_part.value_len().unwrap(), bytes_len as u64);
737 }
738
739 #[test]
740 fn header_percent_encoding() {
741 let name = "start%'\"\r\nßend";
742 let field = Part::text("");
743
744 assert_eq!(
745 PercentEncoding::PathSegment.encode_headers(name, &field.meta),
746 &b"Content-Disposition: form-data; name*=utf-8''start%25'%22%0D%0A%C3%9Fend"[..]
747 );
748
749 assert_eq!(
750 PercentEncoding::AttrChar.encode_headers(name, &field.meta),
751 &b"Content-Disposition: form-data; name*=utf-8''start%25%27%22%0D%0A%C3%9Fend"[..]
752 );
753 }
754}