1use std::{
34 borrow::Cow,
35 io,
36 path::Path,
37 pin::Pin,
38 task::{Context, Poll},
39};
40
41use bytes::{Bytes, BytesMut};
42use futures_util::{StreamExt, stream::Stream};
43use http::header::HeaderValue;
44use http_body::{Frame, SizeHint};
45use http_body_util::StreamBody;
46use mime::Mime;
47use pin_project::pin_project;
48use tokio::io::AsyncRead;
49use tokio_util::io::ReaderStream;
50
51use crate::{body::Body, error::BoxError};
52
53type FrameStream = Pin<Box<dyn Stream<Item = Result<Frame<Bytes>, BoxError>> + Send + Sync>>;
57
58#[pin_project]
62struct SizedStreamBody<S> {
63 #[pin]
64 inner: StreamBody<S>,
65 exact_size: u64,
66}
67
68impl<S> http_body::Body for SizedStreamBody<S>
69where
70 S: Stream<Item = Result<Frame<Bytes>, BoxError>>,
71{
72 type Data = Bytes;
73 type Error = BoxError;
74
75 fn poll_frame(
76 self: Pin<&mut Self>,
77 cx: &mut Context<'_>,
78 ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
79 http_body::Body::poll_frame(self.project().inner, cx)
80 }
81
82 fn is_end_stream(&self) -> bool {
83 http_body::Body::is_end_stream(&self.inner)
84 }
85
86 fn size_hint(&self) -> SizeHint {
87 SizeHint::with_exact(self.exact_size)
88 }
89}
90
91fn exact_size_stream_body<S>(stream: S, exact_size: u64) -> Body
92where
93 S: Stream<Item = Result<Frame<Bytes>, BoxError>> + Send + Sync + 'static,
94{
95 Body::from_body(SizedStreamBody {
96 inner: StreamBody::new(stream),
97 exact_size,
98 })
99}
100
101#[must_use]
107pub struct Form {
108 boundary: String,
109 parts: Vec<(Cow<'static, str>, Part)>,
110}
111
112impl Default for Form {
113 fn default() -> Self {
114 Self::new()
115 }
116}
117
118impl Form {
119 pub fn new() -> Self {
121 Self {
122 boundary: gen_boundary(),
123 parts: Vec::new(),
124 }
125 }
126
127 pub fn boundary(&self) -> &str {
129 &self.boundary
130 }
131
132 pub fn text<N, V>(self, name: N, value: V) -> Self
136 where
137 N: Into<Cow<'static, str>>,
138 V: Into<Cow<'static, str>>,
139 {
140 self.part(name, Part::text(value))
141 }
142
143 pub fn part<N>(mut self, name: N, part: Part) -> Self
145 where
146 N: Into<Cow<'static, str>>,
147 {
148 self.parts.push((name.into(), part));
149 self
150 }
151
152 pub async fn file<N, P>(self, name: N, path: P) -> io::Result<Self>
157 where
158 N: Into<Cow<'static, str>>,
159 P: AsRef<Path>,
160 {
161 Ok(self.part(name, Part::file(path).await?))
162 }
163
164 pub(crate) fn content_type(&self) -> HeaderValue {
166 HeaderValue::from_str(&format!("multipart/form-data; boundary={}", self.boundary))
169 .expect("multipart boundary should always be a valid header value")
170 }
171
172 pub(crate) fn into_body(self) -> Body {
174 let boundary = self.boundary;
175
176 if self.parts.iter().all(|(_, part)| part.data.is_in_memory()) {
182 let cap = self
186 .parts
187 .iter()
188 .map(|(name, part)| {
189 part.data.as_bytes().map_or(0, Bytes::len)
190 + name.len()
191 + part.file_name.as_ref().map_or(0, |f| f.len())
192 + part.mime.as_ref().map_or(0, |m| m.as_ref().len())
193 + boundary.len()
194 + 96
195 })
196 .sum::<usize>()
197 + boundary.len()
198 + 8;
199 let mut buf = BytesMut::with_capacity(cap);
200 for (name, part) in &self.parts {
201 buf.extend_from_slice(&part.encode_header(&boundary, name));
202 if let Some(bytes) = part.data.as_bytes() {
204 buf.extend_from_slice(bytes);
205 }
206 buf.extend_from_slice(b"\r\n");
207 }
208 buf.extend_from_slice(format!("--{boundary}--\r\n").as_bytes());
209 return Body::from(buf.freeze());
210 }
211
212 let closing_boundary = Bytes::from(format!("--{boundary}--\r\n"));
219 let mut exact_len = Some(closing_boundary.len() as u64);
220 let mut streams: Vec<FrameStream> = Vec::with_capacity(self.parts.len() * 3 + 1);
221
222 for (name, part) in self.parts {
223 let header = part.encode_header(&boundary, &name);
224 exact_len = exact_len.and_then(|total| {
225 let data_len = part.len()?;
226 total
227 .checked_add(header.len() as u64)?
228 .checked_add(data_len)?
229 .checked_add(2)
230 });
231 streams.push(once_frame(header));
232 streams.push(part.data.into_stream());
233 streams.push(once_frame(Bytes::from_static(b"\r\n")));
234 }
235 streams.push(once_frame(closing_boundary));
236
237 let stream = futures_util::stream::iter(streams).flatten();
238 match exact_len {
239 Some(exact_len) => exact_size_stream_body(stream, exact_len),
240 None => Body::from_stream(stream),
241 }
242 }
243}
244
245#[must_use]
252pub struct Part {
253 data: PartData,
254 length: Option<u64>,
255 file_name: Option<Cow<'static, str>>,
256 mime: Option<Mime>,
257}
258
259enum PartData {
260 Bytes(Bytes),
261 Stream(FrameStream),
262}
263
264impl PartData {
265 fn into_stream(self) -> FrameStream {
266 match self {
267 PartData::Bytes(bytes) => once_frame(bytes),
268 PartData::Stream(stream) => stream,
269 }
270 }
271
272 fn len(&self) -> Option<u64> {
273 match self {
274 PartData::Bytes(bytes) => Some(bytes.len() as u64),
275 PartData::Stream(_) => None,
276 }
277 }
278
279 fn is_in_memory(&self) -> bool {
281 matches!(self, PartData::Bytes(_))
282 }
283
284 fn as_bytes(&self) -> Option<&Bytes> {
286 match self {
287 PartData::Bytes(bytes) => Some(bytes),
288 PartData::Stream(_) => None,
289 }
290 }
291}
292
293impl Part {
294 pub fn text<T>(value: T) -> Self
296 where
297 T: Into<Cow<'static, str>>,
298 {
299 let bytes = match value.into() {
300 Cow::Borrowed(s) => Bytes::from_static(s.as_bytes()),
301 Cow::Owned(s) => Bytes::from(s),
302 };
303 Self::new(PartData::Bytes(bytes))
304 }
305
306 pub fn bytes<T>(value: T) -> Self
308 where
309 T: Into<Bytes>,
310 {
311 Self::new(PartData::Bytes(value.into()))
312 }
313
314 pub fn reader<R>(reader: R) -> Self
319 where
320 R: AsyncRead + Send + Sync + 'static,
321 {
322 let stream =
323 ReaderStream::new(reader).map(|res| res.map(Frame::data).map_err(BoxError::from));
324 Self::new(PartData::Stream(Box::pin(stream)))
325 }
326
327 pub async fn file<P>(path: P) -> io::Result<Self>
333 where
334 P: AsRef<Path>,
335 {
336 let path = path.as_ref();
337 let file_name = path
338 .file_name()
339 .map(|name| name.to_string_lossy().into_owned());
340 let mime = mime_guess::from_path(path).first();
341 let file = tokio::fs::File::open(path).await?;
342 let file_len = file.metadata().await?.len();
343
344 let mut part = Self::reader(file);
345 part.length = Some(file_len);
348 if let Some(file_name) = file_name {
349 part = part.file_name(file_name);
350 }
351 if let Some(mime) = mime {
352 part = part.mime(mime);
353 }
354 Ok(part)
355 }
356
357 fn new(data: PartData) -> Self {
358 let length = data.len();
359 Self {
360 data,
361 length,
362 file_name: None,
363 mime: None,
364 }
365 }
366
367 fn len(&self) -> Option<u64> {
368 self.length
369 }
370
371 pub fn file_name<T>(mut self, file_name: T) -> Self
373 where
374 T: Into<Cow<'static, str>>,
375 {
376 self.file_name = Some(file_name.into());
377 self
378 }
379
380 pub fn mime_str(self, mime: &str) -> Result<Self, mime::FromStrError> {
386 Ok(self.mime(mime.parse()?))
387 }
388
389 pub fn mime(mut self, mime: Mime) -> Self {
391 self.mime = Some(mime);
392 self
393 }
394
395 fn encode_header(&self, boundary: &str, name: &str) -> Bytes {
398 let cap = 96
402 + boundary.len()
403 + name.len()
404 + self.file_name.as_ref().map_or(0, |f| f.len() + 16)
405 + self.mime.as_ref().map_or(0, |m| m.as_ref().len() + 16);
406 let mut buf = BytesMut::with_capacity(cap);
407 buf.extend_from_slice(b"--");
408 buf.extend_from_slice(boundary.as_bytes());
409 buf.extend_from_slice(b"\r\nContent-Disposition: form-data; name=\"");
410 extend_escaped(&mut buf, name);
411 buf.extend_from_slice(b"\"");
412 if let Some(file_name) = &self.file_name {
413 buf.extend_from_slice(b"; filename=\"");
414 extend_escaped(&mut buf, file_name);
415 buf.extend_from_slice(b"\"");
416 }
417 buf.extend_from_slice(b"\r\n");
418 if let Some(mime) = &self.mime {
419 buf.extend_from_slice(b"Content-Type: ");
420 buf.extend_from_slice(mime.as_ref().as_bytes());
421 buf.extend_from_slice(b"\r\n");
422 }
423 buf.extend_from_slice(b"\r\n");
424 buf.freeze()
425 }
426}
427
428fn once_frame(bytes: Bytes) -> FrameStream {
430 Box::pin(futures_util::stream::once(
431 async move { Ok(Frame::data(bytes)) },
432 ))
433}
434
435fn extend_escaped(buf: &mut BytesMut, value: &str) {
443 let bytes = value.as_bytes();
444 let mut start = 0;
448 for (i, &byte) in bytes.iter().enumerate() {
449 let replacement: &[u8] = match byte {
450 b'\\' => b"\\\\",
451 b'"' => b"\\\"",
452 b'\r' | b'\n' => b" ",
453 _ => continue,
454 };
455 buf.extend_from_slice(&bytes[start..i]);
456 buf.extend_from_slice(replacement);
457 start = i + 1;
458 }
459 buf.extend_from_slice(&bytes[start..]);
460}
461
462fn gen_boundary() -> String {
469 use std::sync::atomic::{AtomicU64, Ordering};
470
471 static COUNTER: AtomicU64 = AtomicU64::new(0);
472
473 let rand = rand::random::<u64>();
474 let seq = COUNTER.fetch_add(1, Ordering::Relaxed);
475
476 format!("volo-http-boundary-{rand:016x}{seq:016x}")
477}
478
479#[cfg(test)]
480mod tests {
481 use http_body_util::BodyExt;
482 use tempfile::NamedTempFile;
483
484 use super::*;
485
486 async fn body_to_string(body: Body) -> String {
487 let bytes = body.collect().await.unwrap().to_bytes();
488 String::from_utf8(bytes.to_vec()).unwrap()
489 }
490
491 #[tokio::test]
492 async fn encode_text_fields() {
493 let form = Form::new().text("key1", "val1").text("key2", "val2");
494 let boundary = form.boundary().to_owned();
495 let content_type = form.content_type();
496 let body = body_to_string(form.into_body()).await;
497
498 assert_eq!(
499 content_type.to_str().unwrap(),
500 format!("multipart/form-data; boundary={boundary}")
501 );
502 let expected = format!(
503 "--{boundary}\r\nContent-Disposition: form-data; \
504 name=\"key1\"\r\n\r\nval1\r\n--{boundary}\r\nContent-Disposition: form-data; \
505 name=\"key2\"\r\n\r\nval2\r\n--{boundary}--\r\n"
506 );
507 assert_eq!(body, expected);
508 }
509
510 #[tokio::test]
511 async fn encode_reader_part_with_metadata() {
512 let form = Form::new().part(
513 "file",
514 Part::reader(std::io::Cursor::new(b"file-content".to_vec()))
515 .file_name("a.txt")
516 .mime_str("text/plain")
517 .unwrap(),
518 );
519 let boundary = form.boundary().to_owned();
520 let body = body_to_string(form.into_body()).await;
521
522 let expected = format!(
523 "--{boundary}\r\nContent-Disposition: form-data; name=\"file\"; \
524 filename=\"a.txt\"\r\nContent-Type: \
525 text/plain\r\n\r\nfile-content\r\n--{boundary}--\r\n"
526 );
527 assert_eq!(body, expected);
528 }
529
530 #[tokio::test]
531 async fn in_memory_form_has_known_length() {
532 use http_body::Body as _;
533
534 let form = Form::new()
537 .text("key", "value")
538 .part("bytes", Part::bytes(&b"raw-bytes"[..]).file_name("a.bin"));
539 let body = form.into_body();
540
541 let hint = body.size_hint();
542 let exact = hint
543 .exact()
544 .expect("in-memory form should have a known length");
545 let encoded = body.collect().await.unwrap().to_bytes();
548 assert_eq!(exact, encoded.len() as u64);
549 }
550
551 #[tokio::test]
552 async fn streaming_form_has_unknown_length() {
553 use http_body::Body as _;
554
555 let form = Form::new().text("key", "value").part(
558 "file",
559 Part::reader(std::io::Cursor::new(b"streamed".to_vec())),
560 );
561 let body = form.into_body();
562
563 assert!(body.size_hint().exact().is_none());
564 }
565
566 #[tokio::test]
567 async fn file_backed_form_has_known_length() {
568 use http_body::Body as _;
569
570 let temp = NamedTempFile::new().unwrap();
571 tokio::fs::write(temp.path(), b"file-content-from-disk")
572 .await
573 .unwrap();
574
575 let form = Form::new()
576 .text("key", "value")
577 .part("file", Part::file(temp.path()).await.unwrap());
578 let body = form.into_body();
579
580 let exact = body
581 .size_hint()
582 .exact()
583 .expect("file-backed form should keep a known length");
584 let encoded = body.collect().await.unwrap().to_bytes();
585 assert_eq!(exact, encoded.len() as u64);
586 }
587
588 #[test]
589 fn boundaries_are_unique_and_valid() {
590 let a = gen_boundary();
591 let b = gen_boundary();
592 assert_ne!(a, b);
594 assert!(a.len() <= 70);
596 assert!(
597 a.bytes().all(|c| c.is_ascii_alphanumeric() || c == b'-'),
598 "boundary contains an invalid character: {a}"
599 );
600 }
601
602 #[test]
603 fn escape_special_chars() {
604 let mut buf = BytesMut::new();
605 extend_escaped(&mut buf, "a\"b\\c\r\nd");
606 assert_eq!(&buf[..], b"a\\\"b\\\\c d");
608
609 let mut buf = BytesMut::new();
611 extend_escaped(&mut buf, "plain_name.txt");
612 assert_eq!(&buf[..], b"plain_name.txt");
613
614 let mut buf = BytesMut::new();
616 extend_escaped(&mut buf, "");
617 assert_eq!(&buf[..], b"");
618
619 let mut buf = BytesMut::new();
620 extend_escaped(&mut buf, "\"ab\"");
621 assert_eq!(&buf[..], b"\\\"ab\\\"");
622 }
623
624 #[tokio::test]
625 async fn quoted_name_roundtrips_through_multer() {
626 let form = Form::new().part("my\"field", Part::text("value").file_name("a\"b.txt"));
634 let boundary = form.boundary().to_owned();
635 let bytes = form.into_body().collect().await.unwrap().to_bytes();
636
637 let stream =
638 futures_util::stream::once(async move { Ok::<_, std::convert::Infallible>(bytes) });
639 let mut multipart = multer::Multipart::new(stream, boundary);
640 let field = multipart.next_field().await.unwrap().unwrap();
641
642 assert_eq!(field.name().unwrap(), "my\"field");
643 assert_eq!(field.file_name().unwrap(), "a\"b.txt");
644 assert_eq!(field.bytes().await.unwrap(), &b"value"[..]);
645 }
646}