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