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::stream;
605 use futures_util::TryStreamExt;
606 use std::future;
607 use tokio::{self, runtime};
608
609 #[test]
610 fn form_empty() {
611 let form = Form::new();
612
613 let rt = runtime::Builder::new_current_thread()
614 .enable_all()
615 .build()
616 .expect("new rt");
617 let body = form.stream().into_stream();
618 let s = body.map_ok(|try_c| try_c.to_vec()).try_concat();
619
620 let out = rt.block_on(s);
621 assert!(out.unwrap().is_empty());
622 }
623
624 #[test]
625 fn stream_to_end() {
626 let mut form = Form::new()
627 .part(
628 "reader1",
629 Part::stream(Body::stream(stream::once(future::ready::<
630 Result<String, crate::Error>,
631 >(Ok(
632 "part1".to_owned()
633 ))))),
634 )
635 .part("key1", Part::text("value1"))
636 .part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
637 .part(
638 "reader2",
639 Part::stream(Body::stream(stream::once(future::ready::<
640 Result<String, crate::Error>,
641 >(Ok(
642 "part2".to_owned()
643 ))))),
644 )
645 .part("key3", Part::text("value3").file_name("filename"));
646 form.inner.boundary = "boundary".to_string();
647 let expected = "--boundary\r\n\
648 Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
649 part1\r\n\
650 --boundary\r\n\
651 Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
652 value1\r\n\
653 --boundary\r\n\
654 Content-Disposition: form-data; name=\"key2\"\r\n\
655 Content-Type: image/bmp\r\n\r\n\
656 value2\r\n\
657 --boundary\r\n\
658 Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
659 part2\r\n\
660 --boundary\r\n\
661 Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
662 value3\r\n--boundary--\r\n";
663 let rt = runtime::Builder::new_current_thread()
664 .enable_all()
665 .build()
666 .expect("new rt");
667 let body = form.stream().into_stream();
668 let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat();
669
670 let out = rt.block_on(s).unwrap();
671 println!(
673 "START REAL\n{}\nEND REAL",
674 std::str::from_utf8(&out).unwrap()
675 );
676 println!("START EXPECTED\n{expected}\nEND EXPECTED");
677 assert_eq!(std::str::from_utf8(&out).unwrap(), expected);
678 }
679
680 #[test]
681 fn stream_to_end_with_header() {
682 let mut part = Part::text("value2").mime(mime::IMAGE_BMP);
683 let mut headers = HeaderMap::new();
684 headers.insert("Hdr3", "/a/b/c".parse().unwrap());
685 part = part.headers(headers);
686 let mut form = Form::new().part("key2", part);
687 form.inner.boundary = "boundary".to_string();
688 let expected = "--boundary\r\n\
689 Content-Disposition: form-data; name=\"key2\"\r\n\
690 Content-Type: image/bmp\r\n\
691 hdr3: /a/b/c\r\n\
692 \r\n\
693 value2\r\n\
694 --boundary--\r\n";
695 let rt = runtime::Builder::new_current_thread()
696 .enable_all()
697 .build()
698 .expect("new rt");
699 let body = form.stream().into_stream();
700 let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat();
701
702 let out = rt.block_on(s).unwrap();
703 println!(
705 "START REAL\n{}\nEND REAL",
706 std::str::from_utf8(&out).unwrap()
707 );
708 println!("START EXPECTED\n{expected}\nEND EXPECTED");
709 assert_eq!(std::str::from_utf8(&out).unwrap(), expected);
710 }
711
712 #[test]
713 fn correct_content_length() {
714 let stream_data = b"just some stream data";
716 let stream_len = stream_data.len();
717 let stream_data = stream_data
718 .chunks(3)
719 .map(|c| Ok::<_, std::io::Error>(Bytes::from(c)));
720 let the_stream = futures_util::stream::iter(stream_data);
721
722 let bytes_data = b"some bytes data".to_vec();
723 let bytes_len = bytes_data.len();
724
725 let stream_part = Part::stream_with_length(Body::stream(the_stream), stream_len as u64);
726 let body_part = Part::bytes(bytes_data);
727
728 assert_eq!(stream_part.value_len().unwrap(), stream_len as u64);
730
731 assert_eq!(body_part.value_len().unwrap(), bytes_len as u64);
733 }
734
735 #[test]
736 fn header_percent_encoding() {
737 let name = "start%'\"\r\nßend";
738 let field = Part::text("");
739
740 assert_eq!(
741 PercentEncoding::PathSegment.encode_headers(name, &field.meta),
742 &b"Content-Disposition: form-data; name*=utf-8''start%25'%22%0D%0A%C3%9Fend"[..]
743 );
744
745 assert_eq!(
746 PercentEncoding::AttrChar.encode_headers(name, &field.meta),
747 &b"Content-Disposition: form-data; name*=utf-8''start%25%27%22%0D%0A%C3%9Fend"[..]
748 );
749 }
750}