1use std::borrow::Cow;
2use std::fmt;
3use std::fmt::{Display, Formatter};
4use std::pin::Pin;
5
6use bytes::Bytes;
7use http::HeaderMap;
8use mime_guess::Mime;
9use percent_encoding::{self, AsciiSet, NON_ALPHANUMERIC};
10
11use futures_core::Stream;
12use futures_util::{future, stream, StreamExt, TryStreamExt};
13use hyper::Body;
14use uuid::Uuid;
15
16#[derive(Debug)]
17pub struct Error(pub String);
18
19pub type Result<T, E = Error> = std::result::Result<T, E>;
20
21impl Display for Error {
22 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
23 f.write_str(self.0.as_str())
24 }
25}
26
27impl std::error::Error for Error {}
28
29pub struct Form {
31 inner: FormParts<Part>,
32}
33
34pub struct Part {
36 meta: PartMetadata,
37 value: Body,
38 body_length: Option<u64>,
39}
40
41pub(crate) struct FormParts<P> {
42 pub(crate) boundary: String,
43 pub(crate) computed_headers: Vec<Vec<u8>>,
44 pub(crate) fields: Vec<(Cow<'static, str>, P)>,
45 pub(crate) percent_encoding: PercentEncoding,
46}
47
48pub(crate) struct PartMetadata {
49 mime: Option<Mime>,
50 file_name: Option<Cow<'static, str>>,
51 pub(crate) headers: HeaderMap,
52}
53
54pub(crate) trait PartProps {
55 fn value_len(&self) -> Option<u64>;
56 fn metadata(&self) -> &PartMetadata;
57}
58
59impl Default for Form {
62 fn default() -> Self {
63 Self::new()
64 }
65}
66
67impl Form {
68 pub fn new() -> Form {
70 Form {
71 inner: FormParts::new(),
72 }
73 }
74
75 #[inline]
77 pub fn boundary(&self) -> &str {
78 self.inner.boundary()
79 }
80
81 pub fn text<T, U>(self, name: T, value: U) -> Form
91 where
92 T: Into<Cow<'static, str>>,
93 U: Into<Cow<'static, str>>,
94 {
95 self.part(name, Part::text(value))
96 }
97
98 pub fn part<T>(self, name: T, part: Part) -> Form
100 where
101 T: Into<Cow<'static, str>>,
102 {
103 self.with_inner(move |inner| inner.part(name, part))
104 }
105
106 pub fn percent_encode_path_segment(self) -> Form {
108 self.with_inner(|inner| inner.percent_encode_path_segment())
109 }
110
111 pub fn percent_encode_attr_chars(self) -> Form {
113 self.with_inner(|inner| inner.percent_encode_attr_chars())
114 }
115
116 pub fn percent_encode_noop(self) -> Form {
118 self.with_inner(|inner| inner.percent_encode_noop())
119 }
120
121 pub fn stream(mut self) -> Body {
123 if self.inner.fields.is_empty() {
124 return Body::empty();
125 }
126
127 let (name, part) = self.inner.fields.remove(0);
129 let start = Box::pin(self.part_stream(name, part))
130 as Pin<Box<dyn Stream<Item=crate::Result<Bytes>> + Send + Sync>>;
131
132 let fields = self.inner.take_fields();
133 let stream = fields.into_iter().fold(start, |memo, (name, part)| {
135 let part_stream = self.part_stream(name, part);
136 Box::pin(memo.chain(part_stream))
137 as Pin<Box<dyn Stream<Item=crate::Result<Bytes>> + Send + Sync>>
138 });
139 let last = stream::once(future::ready(Ok(
141 format!("--{}--\r\n", self.boundary()).into()
142 )));
143 Body::wrap_stream(stream.chain(last))
144 }
145
146 pub(crate) fn part_stream<T>(
148 &mut self,
149 name: T,
150 part: Part,
151 ) -> impl Stream<Item=Result<Bytes, crate::Error>>
152 where
153 T: Into<Cow<'static, str>>,
154 {
155 let boundary = stream::once(future::ready(Ok(
157 format!("--{}\r\n", self.boundary()).into()
158 )));
159 let header = stream::once(future::ready(Ok({
161 let mut h = self
162 .inner
163 .percent_encoding
164 .encode_headers(&name.into(), &part.meta);
165 h.extend_from_slice(b"\r\n\r\n");
166 h.into()
167 })));
168 boundary
170 .chain(header)
171 .chain(part.value.into_stream().map_err(|e| Error("".to_string())))
172 .chain(stream::once(future::ready(Ok("\r\n".into()))))
173 }
174
175 pub(crate) fn compute_length(&mut self) -> Option<u64> {
176 self.inner.compute_length()
177 }
178
179 fn with_inner<F>(self, func: F) -> Self
180 where
181 F: FnOnce(FormParts<Part>) -> FormParts<Part>,
182 {
183 Form {
184 inner: func(self.inner),
185 }
186 }
187}
188
189impl fmt::Debug for Form {
190 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
191 self.inner.fmt_fields("Form", f)
192 }
193}
194
195impl Part {
198 pub fn text<T>(value: T) -> Part
200 where
201 T: Into<Cow<'static, str>>,
202 {
203 let body = match value.into() {
204 Cow::Borrowed(slice) => Body::from(slice),
205 Cow::Owned(string) => Body::from(string),
206 };
207 Part::new(body, None)
208 }
209
210 pub fn bytes<T>(value: T) -> Part
212 where
213 T: Into<Cow<'static, [u8]>>,
214 {
215 let (body, len) = match value.into() {
216 Cow::Borrowed(slice) => (Body::from(slice), slice.len()),
217 Cow::Owned(vec) => {
218 let len = vec.len();
219 (Body::from(vec), len)
220 }
221 };
222 Part::new(body, Some(len as u64))
223 }
224
225 pub fn stream<T: Into<Body>>(value: T) -> Part {
227 Part::new(value.into(), None)
228 }
229
230 pub fn stream_with_length<T: Into<Body>>(value: T, length: u64) -> Part {
234 Part::new(value.into(), Some(length))
235 }
236
237 fn new(value: Body, body_length: Option<u64>) -> Part {
238 Part {
239 meta: PartMetadata::new(),
240 value,
241 body_length,
242 }
243 }
244
245 pub fn mime_str(self, mime: &str) -> crate::Result<Part> {
247 Ok(self.mime(mime.parse().map_err(|e| crate::Error("e".to_string()))?))
248 }
249
250 fn mime(self, mime: Mime) -> Part {
252 self.with_inner(move |inner| inner.mime(mime))
253 }
254
255 pub fn file_name<T>(self, filename: T) -> Part
257 where
258 T: Into<Cow<'static, str>>,
259 {
260 self.with_inner(move |inner| inner.file_name(filename))
261 }
262
263 fn with_inner<F>(self, func: F) -> Self
264 where
265 F: FnOnce(PartMetadata) -> PartMetadata,
266 {
267 Part {
268 meta: func(self.meta),
269 ..self
270 }
271 }
272
273 pub fn headers(&self) -> &HeaderMap {
274 &self.meta.headers
275 }
276
277 pub fn headers_mut(&mut self) -> &mut HeaderMap {
279 &mut self.meta.headers
280 }
281}
282
283impl fmt::Debug for Part {
284 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
285 let mut dbg = f.debug_struct("Part");
286 dbg.field("value", &self.value);
287 self.meta.fmt_fields(&mut dbg);
288 dbg.finish()
289 }
290}
291
292impl PartProps for Part {
293 fn value_len(&self) -> Option<u64> {
294 if self.body_length.is_some() {
295 self.body_length
296 } else {
297 Some(0)
299 }
300 }
301
302 fn metadata(&self) -> &PartMetadata {
303 &self.meta
304 }
305}
306
307impl<P: PartProps> FormParts<P> {
310 pub(crate) fn new() -> Self {
311 FormParts {
312 boundary: gen_boundary(),
313 computed_headers: Vec::new(),
314 fields: Vec::new(),
315 percent_encoding: PercentEncoding::PathSegment,
316 }
317 }
318
319 pub(crate) fn boundary(&self) -> &str {
320 &self.boundary
321 }
322
323 pub(crate) fn part<T>(mut self, name: T, part: P) -> Self
325 where
326 T: Into<Cow<'static, str>>,
327 {
328 self.fields.push((name.into(), part));
329 self
330 }
331
332 pub(crate) fn percent_encode_path_segment(mut self) -> Self {
334 self.percent_encoding = PercentEncoding::PathSegment;
335 self
336 }
337
338 pub(crate) fn percent_encode_attr_chars(mut self) -> Self {
340 self.percent_encoding = PercentEncoding::AttrChar;
341 self
342 }
343
344 pub(crate) fn percent_encode_noop(mut self) -> Self {
346 self.percent_encoding = PercentEncoding::NoOp;
347 self
348 }
349
350 pub(crate) fn compute_length(&mut self) -> Option<u64> {
354 let mut length = 0u64;
355 for &(ref name, ref field) in self.fields.iter() {
356 match field.value_len() {
357 Some(value_length) => {
358 let header = self.percent_encoding.encode_headers(name, field.metadata());
361 let header_length = header.len();
362 self.computed_headers.push(header);
363 length += 2
368 + self.boundary().len() as u64
369 + 2
370 + header_length as u64
371 + 4
372 + value_length
373 + 2
374 }
375 _ => return None,
376 }
377 }
378 if !self.fields.is_empty() {
380 length += 2 + self.boundary().len() as u64 + 4
381 }
382 Some(length)
383 }
384
385 fn take_fields(&mut self) -> Vec<(Cow<'static, str>, P)> {
387 std::mem::replace(&mut self.fields, Vec::new())
388 }
389}
390
391impl<P: fmt::Debug> FormParts<P> {
392 pub(crate) fn fmt_fields(&self, ty_name: &'static str, f: &mut fmt::Formatter) -> fmt::Result {
393 f.debug_struct(ty_name)
394 .field("boundary", &self.boundary)
395 .field("parts", &self.fields)
396 .finish()
397 }
398}
399
400impl PartMetadata {
403 pub(crate) fn new() -> Self {
404 PartMetadata {
405 mime: None,
406 file_name: None,
407 headers: HeaderMap::default(),
408 }
409 }
410
411 pub(crate) fn mime(mut self, mime: Mime) -> Self {
412 self.mime = Some(mime);
413 self
414 }
415
416 pub(crate) fn file_name<T>(mut self, filename: T) -> Self
417 where
418 T: Into<Cow<'static, str>>,
419 {
420 self.file_name = Some(filename.into());
421 self
422 }
423}
424
425impl PartMetadata {
426 pub(crate) fn fmt_fields<'f, 'fa, 'fb>(
427 &self,
428 debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>,
429 ) -> &'f mut fmt::DebugStruct<'fa, 'fb> {
430 debug_struct
431 .field("mime", &self.mime)
432 .field("file_name", &self.file_name)
433 .field("headers", &self.headers)
434 }
435}
436
437const FRAGMENT_ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS
439 .add(b' ')
440 .add(b'"')
441 .add(b'<')
442 .add(b'>')
443 .add(b'`');
444
445const PATH_ENCODE_SET: &AsciiSet = &FRAGMENT_ENCODE_SET.add(b'#').add(b'?').add(b'{').add(b'}');
447
448const PATH_SEGMENT_ENCODE_SET: &AsciiSet = &PATH_ENCODE_SET.add(b'/').add(b'%');
449
450const ATTR_CHAR_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC
452 .remove(b'!')
453 .remove(b'#')
454 .remove(b'$')
455 .remove(b'&')
456 .remove(b'+')
457 .remove(b'-')
458 .remove(b'.')
459 .remove(b'^')
460 .remove(b'_')
461 .remove(b'`')
462 .remove(b'|')
463 .remove(b'~');
464
465pub(crate) enum PercentEncoding {
466 PathSegment,
467 AttrChar,
468 NoOp,
469}
470
471impl PercentEncoding {
472 pub(crate) fn encode_headers(&self, name: &str, field: &PartMetadata) -> Vec<u8> {
473 let s = format!(
474 "Content-Disposition: form-data; {}{}{}",
475 self.format_parameter("name", name),
476 match field.file_name {
477 Some(ref file_name) => format!("; {}", self.format_filename(file_name)),
478 None => String::new(),
479 },
480 match field.mime {
481 Some(ref mime) => format!("\r\nContent-Type: {}", mime),
482 None => "".to_string(),
483 },
484 );
485 field
486 .headers
487 .iter()
488 .fold(s.into_bytes(), |mut header, (k, v)| {
489 header.extend_from_slice(b"\r\n");
490 header.extend_from_slice(k.as_str().as_bytes());
491 header.extend_from_slice(b": ");
492 header.extend_from_slice(v.as_bytes());
493 header
494 })
495 }
496
497 fn format_filename(&self, filename: &str) -> String {
500 let legal_filename = filename
501 .replace("\\", "\\\\")
502 .replace("\"", "\\\"")
503 .replace("\r", "\\\r")
504 .replace("\n", "\\\n");
505 format!("filename=\"{}\"", legal_filename)
506 }
507
508 fn format_parameter(&self, name: &str, value: &str) -> String {
509 let legal_value = match *self {
510 PercentEncoding::PathSegment => {
511 percent_encoding::utf8_percent_encode(value, PATH_SEGMENT_ENCODE_SET).to_string()
512 }
513 PercentEncoding::AttrChar => {
514 percent_encoding::utf8_percent_encode(value, ATTR_CHAR_ENCODE_SET).to_string()
515 }
516 PercentEncoding::NoOp => value.to_string(),
517 };
518 if value.len() == legal_value.len() {
519 format!("{}=\"{}\"", name, value)
521 } else {
522 format!("{}*=utf-8''{}", name, legal_value)
524 }
525 }
526}
527
528fn gen_boundary() -> String {
529 Uuid::new_v4().to_string()
530}
531
532#[cfg(test)]
533mod tests {
534 use super::*;
535 use futures_util::TryStreamExt;
536 use futures_util::{future, stream};
537 use tokio::{self, runtime};
538
539 #[test]
540 fn form_empty() {
541 let form = Form::new();
542
543 let rt = runtime::Builder::new_current_thread()
544 .enable_all()
545 .build()
546 .expect("new rt");
547 let body = form.stream().into_stream();
548 let s = body.map_ok(|try_c| try_c.to_vec()).try_concat();
549
550 let out = rt.block_on(s);
551 assert!(out.unwrap().is_empty());
552 }
553
554 #[test]
555 fn stream_to_end() {
556 let mut form = Form::new()
557 .part(
558 "reader1",
559 Part::stream(Body::wrap_stream(stream::once(future::ready::<
560 Result<String, crate::Error>,
561 >(Ok(
562 "part1".to_owned()
563 ))))),
564 )
565 .part("key1", Part::text("value1"))
566 .part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
567 .part(
568 "reader2",
569 Part::stream(Body::wrap_stream(stream::once(future::ready::<
570 Result<String, crate::Error>,
571 >(Ok(
572 "part2".to_owned()
573 ))))),
574 )
575 .part("key3", Part::text("value3").file_name("filename"));
576 form.inner.boundary = "boundary".to_string();
577 let expected = "--boundary\r\n\
578 Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
579 part1\r\n\
580 --boundary\r\n\
581 Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
582 value1\r\n\
583 --boundary\r\n\
584 Content-Disposition: form-data; name=\"key2\"\r\n\
585 Content-Type: image/bmp\r\n\r\n\
586 value2\r\n\
587 --boundary\r\n\
588 Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
589 part2\r\n\
590 --boundary\r\n\
591 Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
592 value3\r\n--boundary--\r\n";
593 let rt = runtime::Builder::new_current_thread()
594 .enable_all()
595 .build()
596 .expect("new rt");
597 let body = form.stream().into_stream();
598 let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat();
599
600 let out = rt.block_on(s).unwrap();
601 println!(
603 "START REAL\n{}\nEND REAL",
604 std::str::from_utf8(&out).unwrap()
605 );
606 println!("START EXPECTED\n{}\nEND EXPECTED", expected);
607 assert_eq!(std::str::from_utf8(&out).unwrap(), expected);
608 }
609
610 #[test]
611 fn stream_to_end_with_header() {
612 let mut part = Part::text("value2").mime(mime::IMAGE_BMP);
613 part.meta.headers.insert("Hdr3", "/a/b/c".parse().unwrap());
614 let mut form = Form::new().part("key2", part);
615 form.inner.boundary = "boundary".to_string();
616 let expected = "--boundary\r\n\
617 Content-Disposition: form-data; name=\"key2\"\r\n\
618 Content-Type: image/bmp\r\n\
619 hdr3: /a/b/c\r\n\
620 \r\n\
621 value2\r\n\
622 --boundary--\r\n";
623 let rt = runtime::Builder::new_current_thread()
624 .enable_all()
625 .build()
626 .expect("new rt");
627 let body = form.stream().into_stream();
628 let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat();
629
630 let out = rt.block_on(s).unwrap();
631 println!(
633 "START REAL\n{}\nEND REAL",
634 std::str::from_utf8(&out).unwrap()
635 );
636 println!("START EXPECTED\n{}\nEND EXPECTED", expected);
637 assert_eq!(std::str::from_utf8(&out).unwrap(), expected);
638 }
639
640 #[test]
641 fn correct_content_length() {
642 let stream_data = b"just some stream data";
644 let stream_len = stream_data.len();
645 let stream_data = stream_data
646 .chunks(3)
647 .map(|c| Ok::<_, std::io::Error>(Bytes::from(c)));
648 let the_stream = futures_util::stream::iter(stream_data);
649
650 let bytes_data = b"some bytes data".to_vec();
651 let bytes_len = bytes_data.len();
652
653 let stream_part = Part::stream_with_length(Body::wrap_stream(the_stream), stream_len as u64);
654 let body_part = Part::bytes(bytes_data);
655
656 assert_eq!(stream_part.value_len().unwrap(), stream_len as u64);
658
659 assert_eq!(body_part.value_len().unwrap(), bytes_len as u64);
661 }
662
663 #[test]
664 fn header_percent_encoding() {
665 let name = "start%'\"\r\nßend";
666 let field = Part::text("");
667
668 assert_eq!(
669 PercentEncoding::PathSegment.encode_headers(name, &field.meta),
670 &b"Content-Disposition: form-data; name*=utf-8''start%25'%22%0D%0A%C3%9Fend"[..]
671 );
672
673 assert_eq!(
674 PercentEncoding::AttrChar.encode_headers(name, &field.meta),
675 &b"Content-Disposition: form-data; name*=utf-8''start%25%27%22%0D%0A%C3%9Fend"[..]
676 );
677 }
678 #[tokio::test]
679 async fn reqwest(){
680 let form = Form::new();
681 let mut form = form.text("name", "value");
682 let mut part = Part::text("part");
683 part.headers_mut().insert("test", "test".parse().unwrap());
684
685 form = form.part("part",part);
686
687 let cli = reqwest::Client::builder().build().expect("");
688 let resp = cli.post("http://localhost:9090/").body(form.stream()).send().await;
689 println!("{resp:?}");
690 }
691}