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