1use std::borrow::Cow;
3use std::fmt;
4use std::pin::Pin;
5
6use bytes::Bytes;
7use mime_guess::Mime;
8use percent_encoding::{self, AsciiSet, NON_ALPHANUMERIC};
9
10use futures_core::Stream;
11use futures_util::{future, stream, StreamExt};
12
13use super::Body;
14use crate::header::HeaderMap;
15
16pub struct Form {
18 inner: FormParts<Part>,
19}
20
21pub struct Part {
23 meta: PartMetadata,
24 value: Body,
25 body_length: Option<u64>,
26}
27
28pub(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
35pub(crate) struct PartMetadata {
36 mime: Option<Mime>,
37 file_name: Option<Cow<'static, str>>,
38 pub(crate) headers: HeaderMap,
39}
40
41pub(crate) trait PartProps {
42 fn value_len(&self) -> Option<u64>;
43 fn metadata(&self) -> &PartMetadata;
44}
45
46impl Default for Form {
49 fn default() -> Self {
50 Self::new()
51 }
52}
53
54impl Form {
55 pub fn new() -> Form {
57 Form {
58 inner: FormParts::new(),
59 }
60 }
61
62 #[inline]
64 pub fn boundary(&self) -> &str {
65 self.inner.boundary()
66 }
67
68 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 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 pub fn percent_encode_path_segment(self) -> Form {
95 self.with_inner(|inner| inner.percent_encode_path_segment())
96 }
97
98 pub fn percent_encode_attr_chars(self) -> Form {
100 self.with_inner(|inner| inner.percent_encode_attr_chars())
101 }
102
103 pub fn percent_encode_noop(self) -> Form {
105 self.with_inner(|inner| inner.percent_encode_noop())
106 }
107
108 pub(crate) fn stream(mut self) -> Body {
110 if self.inner.fields.is_empty() {
111 return Body::empty();
112 }
113
114 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 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 let last = stream::once(future::ready(Ok(
128 format!("--{}--\r\n", self.boundary()).into()
129 )));
130 Body::stream(stream.chain(last))
131 }
132
133 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 let boundary = stream::once(future::ready(Ok(
144 format!("--{}\r\n", self.boundary()).into()
145 )));
146 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 boundary
157 .chain(header)
158 .chain(part.value.into_stream())
159 .chain(stream::once(future::ready(Ok("\r\n".into()))))
160 }
161
162 pub(crate) fn compute_length(&mut self) -> Option<u64> {
163 self.inner.compute_length()
164 }
165
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
176impl fmt::Debug for Form {
177 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
178 self.inner.fmt_fields("Form", f)
179 }
180}
181
182impl Part {
185 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 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 pub fn stream<T: Into<Body>>(value: T) -> Part {
211 Part::new(value.into(), None)
212 }
213
214 pub fn stream_with_length<T: Into<Body>>(value: T, length: u64) -> Part {
218 Part::new(value.into(), Some(length))
219 }
220
221 fn new(value: Body, body_length: Option<u64>) -> Part {
222 Part {
223 meta: PartMetadata::new(),
224 value,
225 body_length,
226 }
227 }
228
229 pub fn mime_str(self, mime: &str) -> crate::Result<Part> {
231 Ok(self.mime(mime.parse().map_err(crate::error::builder)?))
232 }
233
234 fn mime(self, mime: Mime) -> Part {
236 self.with_inner(move |inner| inner.mime(mime))
237 }
238
239 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
247 pub fn headers(self, headers: HeaderMap) -> Part {
249 self.with_inner(move |inner| inner.headers(headers))
250 }
251
252 fn with_inner<F>(self, func: F) -> Self
253 where
254 F: FnOnce(PartMetadata) -> PartMetadata,
255 {
256 Part {
257 meta: func(self.meta),
258 ..self
259 }
260 }
261}
262
263impl fmt::Debug for Part {
264 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
265 let mut dbg = f.debug_struct("Part");
266 dbg.field("value", &self.value);
267 self.meta.fmt_fields(&mut dbg);
268 dbg.finish()
269 }
270}
271
272impl PartProps for Part {
273 fn value_len(&self) -> Option<u64> {
274 if self.body_length.is_some() {
275 self.body_length
276 } else {
277 self.value.content_length()
278 }
279 }
280
281 fn metadata(&self) -> &PartMetadata {
282 &self.meta
283 }
284}
285
286impl<P: PartProps> FormParts<P> {
289 pub(crate) fn new() -> Self {
290 FormParts {
291 boundary: gen_boundary(),
292 computed_headers: Vec::new(),
293 fields: Vec::new(),
294 percent_encoding: PercentEncoding::PathSegment,
295 }
296 }
297
298 pub(crate) fn boundary(&self) -> &str {
299 &self.boundary
300 }
301
302 pub(crate) fn part<T>(mut self, name: T, part: P) -> Self
304 where
305 T: Into<Cow<'static, str>>,
306 {
307 self.fields.push((name.into(), part));
308 self
309 }
310
311 pub(crate) fn percent_encode_path_segment(mut self) -> Self {
313 self.percent_encoding = PercentEncoding::PathSegment;
314 self
315 }
316
317 pub(crate) fn percent_encode_attr_chars(mut self) -> Self {
319 self.percent_encoding = PercentEncoding::AttrChar;
320 self
321 }
322
323 pub(crate) fn percent_encode_noop(mut self) -> Self {
325 self.percent_encoding = PercentEncoding::NoOp;
326 self
327 }
328
329 pub(crate) fn compute_length(&mut self) -> Option<u64> {
333 let mut length = 0u64;
334 for &(ref name, ref field) in self.fields.iter() {
335 match field.value_len() {
336 Some(value_length) => {
337 let header = self.percent_encoding.encode_headers(name, field.metadata());
340 let header_length = header.len();
341 self.computed_headers.push(header);
342 length += 2
347 + self.boundary().len() as u64
348 + 2
349 + header_length as u64
350 + 4
351 + value_length
352 + 2
353 }
354 _ => return None,
355 }
356 }
357 if !self.fields.is_empty() {
359 length += 2 + self.boundary().len() as u64 + 4
360 }
361 Some(length)
362 }
363
364 fn take_fields(&mut self) -> Vec<(Cow<'static, str>, P)> {
366 std::mem::replace(&mut self.fields, Vec::new())
367 }
368}
369
370impl<P: fmt::Debug> FormParts<P> {
371 pub(crate) fn fmt_fields(&self, ty_name: &'static str, f: &mut fmt::Formatter) -> fmt::Result {
372 f.debug_struct(ty_name)
373 .field("boundary", &self.boundary)
374 .field("parts", &self.fields)
375 .finish()
376 }
377}
378
379impl PartMetadata {
382 pub(crate) fn new() -> Self {
383 PartMetadata {
384 mime: None,
385 file_name: None,
386 headers: HeaderMap::default(),
387 }
388 }
389
390 pub(crate) fn mime(mut self, mime: Mime) -> Self {
391 self.mime = Some(mime);
392 self
393 }
394
395 pub(crate) fn file_name<T>(mut self, filename: T) -> Self
396 where
397 T: Into<Cow<'static, str>>,
398 {
399 self.file_name = Some(filename.into());
400 self
401 }
402
403 pub(crate) fn headers<T>(mut self, headers: T) -> Self
404 where
405 T: Into<HeaderMap>,
406 {
407 self.headers = headers.into();
408 self
409 }
410}
411
412impl PartMetadata {
413 pub(crate) fn fmt_fields<'f, 'fa, 'fb>(
414 &self,
415 debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>,
416 ) -> &'f mut fmt::DebugStruct<'fa, 'fb> {
417 debug_struct
418 .field("mime", &self.mime)
419 .field("file_name", &self.file_name)
420 .field("headers", &self.headers)
421 }
422}
423
424const FRAGMENT_ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS
426 .add(b' ')
427 .add(b'"')
428 .add(b'<')
429 .add(b'>')
430 .add(b'`');
431
432const PATH_ENCODE_SET: &AsciiSet = &FRAGMENT_ENCODE_SET.add(b'#').add(b'?').add(b'{').add(b'}');
434
435const PATH_SEGMENT_ENCODE_SET: &AsciiSet = &PATH_ENCODE_SET.add(b'/').add(b'%');
436
437const ATTR_CHAR_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC
439 .remove(b'!')
440 .remove(b'#')
441 .remove(b'$')
442 .remove(b'&')
443 .remove(b'+')
444 .remove(b'-')
445 .remove(b'.')
446 .remove(b'^')
447 .remove(b'_')
448 .remove(b'`')
449 .remove(b'|')
450 .remove(b'~');
451
452pub(crate) enum PercentEncoding {
453 PathSegment,
454 AttrChar,
455 NoOp,
456}
457
458impl PercentEncoding {
459 pub(crate) fn encode_headers(&self, name: &str, field: &PartMetadata) -> Vec<u8> {
460 let mut buf = Vec::new();
461 buf.extend_from_slice(b"Content-Disposition: form-data; ");
462
463 match self.percent_encode(name) {
464 Cow::Borrowed(value) => {
465 buf.extend_from_slice(b"name=\"");
467 buf.extend_from_slice(value.as_bytes());
468 buf.extend_from_slice(b"\"");
469 }
470 Cow::Owned(value) => {
471 buf.extend_from_slice(b"name*=utf-8''");
473 buf.extend_from_slice(value.as_bytes());
474 }
475 }
476
477 if let Some(filename) = &field.file_name {
480 buf.extend_from_slice(b"; filename=\"");
481 let legal_filename = filename
482 .replace('\\', "\\\\")
483 .replace('"', "\\\"")
484 .replace('\r', "\\\r")
485 .replace('\n', "\\\n");
486 buf.extend_from_slice(legal_filename.as_bytes());
487 buf.extend_from_slice(b"\"");
488 }
489
490 if let Some(mime) = &field.mime {
491 buf.extend_from_slice(b"\r\nContent-Type: ");
492 buf.extend_from_slice(mime.as_ref().as_bytes());
493 }
494
495 for (k, v) in field.headers.iter() {
496 buf.extend_from_slice(b"\r\n");
497 buf.extend_from_slice(k.as_str().as_bytes());
498 buf.extend_from_slice(b": ");
499 buf.extend_from_slice(v.as_bytes());
500 }
501 buf
502 }
503
504 fn percent_encode<'a>(&self, value: &'a str) -> Cow<'a, str> {
505 use percent_encoding::utf8_percent_encode as percent_encode;
506
507 match self {
508 Self::PathSegment => percent_encode(value, PATH_SEGMENT_ENCODE_SET).into(),
509 Self::AttrChar => percent_encode(value, ATTR_CHAR_ENCODE_SET).into(),
510 Self::NoOp => value.into(),
511 }
512 }
513}
514
515fn gen_boundary() -> String {
516 use crate::util::fast_random as random;
517
518 let a = random();
519 let b = random();
520 let c = random();
521 let d = random();
522
523 format!("{a:016x}-{b:016x}-{c:016x}-{d:016x}")
524}
525
526#[cfg(test)]
527mod tests {
528 use super::*;
529 use futures_util::TryStreamExt;
530 use futures_util::{future, stream};
531 use tokio::{self, runtime};
532
533 #[test]
534 fn form_empty() {
535 let form = Form::new();
536
537 let rt = runtime::Builder::new_current_thread()
538 .enable_all()
539 .build()
540 .expect("new rt");
541 let body = form.stream().into_stream();
542 let s = body.map_ok(|try_c| try_c.to_vec()).try_concat();
543
544 let out = rt.block_on(s);
545 assert!(out.unwrap().is_empty());
546 }
547
548 #[test]
549 fn stream_to_end() {
550 let mut form = Form::new()
551 .part(
552 "reader1",
553 Part::stream(Body::stream(stream::once(future::ready::<
554 Result<String, crate::Error>,
555 >(Ok(
556 "part1".to_owned()
557 ))))),
558 )
559 .part("key1", Part::text("value1"))
560 .part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
561 .part(
562 "reader2",
563 Part::stream(Body::stream(stream::once(future::ready::<
564 Result<String, crate::Error>,
565 >(Ok(
566 "part2".to_owned()
567 ))))),
568 )
569 .part("key3", Part::text("value3").file_name("filename"));
570 form.inner.boundary = "boundary".to_string();
571 let expected = "--boundary\r\n\
572 Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
573 part1\r\n\
574 --boundary\r\n\
575 Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
576 value1\r\n\
577 --boundary\r\n\
578 Content-Disposition: form-data; name=\"key2\"\r\n\
579 Content-Type: image/bmp\r\n\r\n\
580 value2\r\n\
581 --boundary\r\n\
582 Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
583 part2\r\n\
584 --boundary\r\n\
585 Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
586 value3\r\n--boundary--\r\n";
587 let rt = runtime::Builder::new_current_thread()
588 .enable_all()
589 .build()
590 .expect("new rt");
591 let body = form.stream().into_stream();
592 let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat();
593
594 let out = rt.block_on(s).unwrap();
595 println!(
597 "START REAL\n{}\nEND REAL",
598 std::str::from_utf8(&out).unwrap()
599 );
600 println!("START EXPECTED\n{expected}\nEND EXPECTED");
601 assert_eq!(std::str::from_utf8(&out).unwrap(), expected);
602 }
603
604 #[test]
605 fn stream_to_end_with_header() {
606 let mut part = Part::text("value2").mime(mime::IMAGE_BMP);
607 let mut headers = HeaderMap::new();
608 headers.insert("Hdr3", "/a/b/c".parse().unwrap());
609 part = part.headers(headers);
610 let mut form = Form::new().part("key2", part);
611 form.inner.boundary = "boundary".to_string();
612 let expected = "--boundary\r\n\
613 Content-Disposition: form-data; name=\"key2\"\r\n\
614 Content-Type: image/bmp\r\n\
615 hdr3: /a/b/c\r\n\
616 \r\n\
617 value2\r\n\
618 --boundary--\r\n";
619 let rt = runtime::Builder::new_current_thread()
620 .enable_all()
621 .build()
622 .expect("new rt");
623 let body = form.stream().into_stream();
624 let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat();
625
626 let out = rt.block_on(s).unwrap();
627 println!(
629 "START REAL\n{}\nEND REAL",
630 std::str::from_utf8(&out).unwrap()
631 );
632 println!("START EXPECTED\n{expected}\nEND EXPECTED");
633 assert_eq!(std::str::from_utf8(&out).unwrap(), expected);
634 }
635
636 #[test]
637 fn correct_content_length() {
638 let stream_data = b"just some stream data";
640 let stream_len = stream_data.len();
641 let stream_data = stream_data
642 .chunks(3)
643 .map(|c| Ok::<_, std::io::Error>(Bytes::from(c)));
644 let the_stream = futures_util::stream::iter(stream_data);
645
646 let bytes_data = b"some bytes data".to_vec();
647 let bytes_len = bytes_data.len();
648
649 let stream_part = Part::stream_with_length(Body::stream(the_stream), stream_len as u64);
650 let body_part = Part::bytes(bytes_data);
651
652 assert_eq!(stream_part.value_len().unwrap(), stream_len as u64);
654
655 assert_eq!(body_part.value_len().unwrap(), bytes_len as u64);
657 }
658
659 #[test]
660 fn header_percent_encoding() {
661 let name = "start%'\"\r\nßend";
662 let field = Part::text("");
663
664 assert_eq!(
665 PercentEncoding::PathSegment.encode_headers(name, &field.meta),
666 &b"Content-Disposition: form-data; name*=utf-8''start%25'%22%0D%0A%C3%9Fend"[..]
667 );
668
669 assert_eq!(
670 PercentEncoding::AttrChar.encode_headers(name, &field.meta),
671 &b"Content-Disposition: form-data; name*=utf-8''start%25%27%22%0D%0A%C3%9Fend"[..]
672 );
673 }
674}