Skip to content

Commit

Permalink
multipart parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
yoshuawuyts committed Sep 27, 2020
1 parent 0f453a8 commit a03ac12
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 20 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ http = { version = "0.2.0", optional = true }

anyhow = "1.0.26"
cookie = { version = "0.14.0", features = ["percent-encode"] }
futures-core = "0.3.5"
infer = "0.2.3"
pin-project-lite = "0.1.0"
url = { version = "2.1.1", features = ["serde"] }
Expand Down
13 changes: 6 additions & 7 deletions src/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use async_std::io::{self, Cursor};
use serde::{de::DeserializeOwned, Serialize};

use std::fmt::{self, Debug};
use std::path::{Path, PathBuf};
use std::pin::Pin;
use std::task::{Context, Poll};

Expand Down Expand Up @@ -58,7 +57,7 @@ pin_project_lite::pin_project! {
reader: Box<dyn BufRead + Unpin + Send + Sync + 'static>,
mime: Mime,
length: Option<usize>,
pub(crate) file_name: Option<PathBuf>,
pub(crate) file_name: Option<String>,
}
}

Expand Down Expand Up @@ -388,7 +387,7 @@ impl Body {
mime,
length: Some(len as usize),
reader: Box::new(io::BufReader::new(file)),
file_name: Some(path.to_path_buf()),
file_name: Some(path.to_string_lossy().to_string()),
})
}

Expand Down Expand Up @@ -425,14 +424,14 @@ impl Body {
}

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

/// Set the file name of the `Body`.
pub fn set_file_name<P>(&mut self, file_name: Option<P>)
pub fn set_file_name<S>(&mut self, file_name: Option<S>)
where
P: AsRef<Path>,
S: AsRef<str>,
{
self.file_name = file_name.map(|v| v.as_ref().to_owned());
}
Expand Down
7 changes: 3 additions & 4 deletions src/multipart/entry.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::{bail, Body, Mime};

use std::fmt::{self, Debug};
use std::path::Path;

/// A single multipart entry.
///
Expand Down Expand Up @@ -68,14 +67,14 @@ impl Entry {
}

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

/// Set the file name of the `Body`.
pub fn set_file_name<P>(&mut self, file_name: Option<P>)
where
P: AsRef<Path>,
P: AsRef<str>,
{
self.body.set_file_name(file_name);
}
Expand Down
81 changes: 72 additions & 9 deletions src/multipart/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,29 @@
//! }
//! ```

use crate::Body;
use std::task::Context;
use std::task::Poll;
use std::{fmt::Debug, pin::Pin, str::FromStr};

use futures_core::stream::Stream;
use multipart::server::Multipart as Parser;
use std::io::{Cursor, Read};

use crate::{format_err, Mime, Status};
pub use entry::Entry;

mod entry;

/// A multipart response body.
#[derive(Debug)]
pub struct Multipart {
entries: Vec<Entry>,
body: Option<Body>,
body: Option<Parser<Cursor<String>>>,
}

impl Debug for Multipart {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Multipart").finish()
}
}

impl Multipart {
Expand All @@ -53,11 +66,28 @@ impl Multipart {
}

/// Parse a `Body` stream as a `Multipart` instance.
pub fn from_body(body: Body) -> Self {
Self {
pub async fn from_req(req: &mut crate::Request) -> crate::Result<Self> {
let body = req.take_body().into_string().await?;
let boundary = req
.content_type()
.map(|ct| ct.param("boundary").cloned())
.flatten();

let boundary = match boundary {
Some(boundary) => boundary.as_str().to_owned(),
None => {
let mut err =
format_err!("Invalid `Content-Type` header. Expected a `boundary` param");
err.set_status(400);
return Err(err);
}
};

let multipart = Parser::with_body(Cursor::new(body), boundary);
Ok(Self {
entries: vec![],
body: Some(body),
}
body: Some(multipart),
})
}

/// Add a new entry to the `Multipart` instance.
Expand All @@ -69,8 +99,41 @@ impl Multipart {
}
}

// TODO
// impl Stream for Multipart {}
impl Stream for Multipart {
type Item = crate::Result<Entry>;

fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Option<Self::Item>> {
let body = match self.body.as_mut() {
None => return Poll::Ready(None),
Some(body) => body,
};

match body.read_entry() {
Ok(Some(mut field)) => {
let mut body = vec![];
field.data.read_to_end(&mut body).status(400)?;

let mut entry = Entry::new(field.headers.name, body);
entry.set_file_name(field.headers.filename);
let mime = field
.headers
.content_type
.map(|ct| Mime::from_str(&ct.to_string()))
.transpose()?;
entry.set_content_type(mime);

Poll::Ready(Some(Ok(entry)))
}
Ok(None) => Poll::Ready(None),
Err(_e) => {
// TODO: forward error?
let mut err = format_err!("Invalid multipart entry");
err.set_status(400);
Poll::Ready(Some(Err(err)))
}
}
}
}

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

0 comments on commit a03ac12

Please sign in to comment.