Skip to content

Commit a03ac12

Browse files
committed
multipart parsing
1 parent 0f453a8 commit a03ac12

File tree

4 files changed

+82
-20
lines changed

4 files changed

+82
-20
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ http = { version = "0.2.0", optional = true }
3333

3434
anyhow = "1.0.26"
3535
cookie = { version = "0.14.0", features = ["percent-encode"] }
36+
futures-core = "0.3.5"
3637
infer = "0.2.3"
3738
pin-project-lite = "0.1.0"
3839
url = { version = "2.1.1", features = ["serde"] }

src/body.rs

+6-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use async_std::io::{self, Cursor};
33
use serde::{de::DeserializeOwned, Serialize};
44

55
use std::fmt::{self, Debug};
6-
use std::path::{Path, PathBuf};
76
use std::pin::Pin;
87
use std::task::{Context, Poll};
98

@@ -58,7 +57,7 @@ pin_project_lite::pin_project! {
5857
reader: Box<dyn BufRead + Unpin + Send + Sync + 'static>,
5958
mime: Mime,
6059
length: Option<usize>,
61-
pub(crate) file_name: Option<PathBuf>,
60+
pub(crate) file_name: Option<String>,
6261
}
6362
}
6463

@@ -388,7 +387,7 @@ impl Body {
388387
mime,
389388
length: Some(len as usize),
390389
reader: Box::new(io::BufReader::new(file)),
391-
file_name: Some(path.to_path_buf()),
390+
file_name: Some(path.to_string_lossy().to_string()),
392391
})
393392
}
394393

@@ -425,14 +424,14 @@ impl Body {
425424
}
426425

427426
/// Get the file name of the `Body`, if it's set.
428-
pub fn file_name(&self) -> Option<&PathBuf> {
429-
self.file_name.as_ref()
427+
pub fn file_name(&self) -> Option<&str> {
428+
self.file_name.as_ref().map(|s| s.as_str())
430429
}
431430

432431
/// Set the file name of the `Body`.
433-
pub fn set_file_name<P>(&mut self, file_name: Option<P>)
432+
pub fn set_file_name<S>(&mut self, file_name: Option<S>)
434433
where
435-
P: AsRef<Path>,
434+
S: AsRef<str>,
436435
{
437436
self.file_name = file_name.map(|v| v.as_ref().to_owned());
438437
}

src/multipart/entry.rs

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use crate::{bail, Body, Mime};
22

33
use std::fmt::{self, Debug};
4-
use std::path::Path;
54

65
/// A single multipart entry.
76
///
@@ -68,14 +67,14 @@ impl Entry {
6867
}
6968

7069
/// Get the file name of the entry, if it's set.
71-
pub fn file_name(&self) -> Option<&Path> {
72-
self.body.file_name().map(|p| p.as_path())
70+
pub fn file_name(&self) -> Option<&str> {
71+
self.body.file_name()
7372
}
7473

7574
/// Set the file name of the `Body`.
7675
pub fn set_file_name<P>(&mut self, file_name: Option<P>)
7776
where
78-
P: AsRef<Path>,
77+
P: AsRef<str>,
7978
{
8079
self.body.set_file_name(file_name);
8180
}

src/multipart/mod.rs

+72-9
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,29 @@
3131
//! }
3232
//! ```
3333
34-
use crate::Body;
34+
use std::task::Context;
35+
use std::task::Poll;
36+
use std::{fmt::Debug, pin::Pin, str::FromStr};
37+
38+
use futures_core::stream::Stream;
39+
use multipart::server::Multipart as Parser;
40+
use std::io::{Cursor, Read};
41+
42+
use crate::{format_err, Mime, Status};
3543
pub use entry::Entry;
3644

3745
mod entry;
3846

3947
/// A multipart response body.
40-
#[derive(Debug)]
4148
pub struct Multipart {
4249
entries: Vec<Entry>,
43-
body: Option<Body>,
50+
body: Option<Parser<Cursor<String>>>,
51+
}
52+
53+
impl Debug for Multipart {
54+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55+
f.debug_struct("Multipart").finish()
56+
}
4457
}
4558

4659
impl Multipart {
@@ -53,11 +66,28 @@ impl Multipart {
5366
}
5467

5568
/// Parse a `Body` stream as a `Multipart` instance.
56-
pub fn from_body(body: Body) -> Self {
57-
Self {
69+
pub async fn from_req(req: &mut crate::Request) -> crate::Result<Self> {
70+
let body = req.take_body().into_string().await?;
71+
let boundary = req
72+
.content_type()
73+
.map(|ct| ct.param("boundary").cloned())
74+
.flatten();
75+
76+
let boundary = match boundary {
77+
Some(boundary) => boundary.as_str().to_owned(),
78+
None => {
79+
let mut err =
80+
format_err!("Invalid `Content-Type` header. Expected a `boundary` param");
81+
err.set_status(400);
82+
return Err(err);
83+
}
84+
};
85+
86+
let multipart = Parser::with_body(Cursor::new(body), boundary);
87+
Ok(Self {
5888
entries: vec![],
59-
body: Some(body),
60-
}
89+
body: Some(multipart),
90+
})
6191
}
6292

6393
/// Add a new entry to the `Multipart` instance.
@@ -69,8 +99,41 @@ impl Multipart {
6999
}
70100
}
71101

72-
// TODO
73-
// impl Stream for Multipart {}
102+
impl Stream for Multipart {
103+
type Item = crate::Result<Entry>;
104+
105+
fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Option<Self::Item>> {
106+
let body = match self.body.as_mut() {
107+
None => return Poll::Ready(None),
108+
Some(body) => body,
109+
};
110+
111+
match body.read_entry() {
112+
Ok(Some(mut field)) => {
113+
let mut body = vec![];
114+
field.data.read_to_end(&mut body).status(400)?;
115+
116+
let mut entry = Entry::new(field.headers.name, body);
117+
entry.set_file_name(field.headers.filename);
118+
let mime = field
119+
.headers
120+
.content_type
121+
.map(|ct| Mime::from_str(&ct.to_string()))
122+
.transpose()?;
123+
entry.set_content_type(mime);
124+
125+
Poll::Ready(Some(Ok(entry)))
126+
}
127+
Ok(None) => Poll::Ready(None),
128+
Err(_e) => {
129+
// TODO: forward error?
130+
let mut err = format_err!("Invalid multipart entry");
131+
err.set_status(400);
132+
Poll::Ready(Some(Err(err)))
133+
}
134+
}
135+
}
136+
}
74137

75138
// TODO
76139
// impl From<Multipart> for Body {}

0 commit comments

Comments
 (0)