Skip to content

Commit

Permalink
Merge branch 'release/v1.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
rousan committed May 17, 2020
2 parents 123a39a + 81f537a commit d5e168d
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 202 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "multer"
version = "1.0.3"
version = "1.1.0"
description = "An async parser for `multipart/form-data` content-type in Rust."
homepage = "https://github.com/rousan/multer-rs"
repository = "https://github.com/rousan/multer-rs"
Expand Down Expand Up @@ -34,6 +34,7 @@ mime = "0.3"
regex = "1.3"
lazy_static = "1.4.0"
encoding_rs = "0.8"
derive_more = "0.99"

serde = { version = "1.0", optional = true }
serde_json = { version = "1.0", optional = true }
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Add this to your `Cargo.toml`:

```toml
[dependencies]
multer = "1.0"
multer = "1.1"
```

# Basic Example
Expand Down
79 changes: 1 addition & 78 deletions examples/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,15 @@ use futures::TryStreamExt;
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Request, Response, Server};
use multer::{Constraints, Error, Field, Multipart, SizeLimit};
use multer::{ErrorExt, ResultExt};
use std::{convert::Infallible, net::SocketAddr};
use tokio::fs::{File, OpenOptions};
use tokio::io::{AsyncWrite, AsyncWriteExt};

async fn handle(req: Request<Body>) -> Result<Response<Body>, Infallible> {
let mut stream = req.into_body();
// let mut stream = futures::stream::once(async move { Result::<&'static str, Infallible>::Ok("abc") });
// let stream = futures::stream::iter(vec![
// Ok("abc"),
// Err(std::io::Error::new(std::io::ErrorKind::Other, "Heyyyyyyyyyy")),
// ]);

// while let Some(Ok(data)) = stream.next().await {
// print!("{:?}", String::from_utf8_lossy(&*data).to_string())
// }

// let reader =
// tokio::io::stream_reader(stream.map_err(|err| tokio::io::Error::new(tokio::io::ErrorKind::Other, err)));
//
// let mut multipart = Multipart::with_reader(reader, "X-INSOMNIA-BOUNDARY");

let multipart_constraints = Constraints::new()
.allowed_fields(vec!["a"])
.allowed_fields(vec!["a", "b"])
.size_limit(SizeLimit::new().per_field(30).for_field("a", 10));

let mut multipart = Multipart::new_with_constraints(stream, "X-INSOMNIA-BOUNDARY", multipart_constraints);
Expand All @@ -36,70 +21,8 @@ async fn handle(req: Request<Body>) -> Result<Response<Body>, Infallible> {
println!("{:?}", field.name());
let text = field.text().await.unwrap();
println!("{}", text);
// let text = field.text().await.unwrap();
// println!("{}", text);
}

// let mut m = Multipart::new(stream, "X-INSOMNIA-BOUNDARY");
//
// // let mut i = 0;
// 'outer: while let Some(field) = m.next_field().await.context("No field").unwrap() {
// // i += 1;
// println!("{:?}", field.headers());
//
// // let mut file = OpenOptions::new()
// // .create(true)
// // .write(true)
// // .open(format!("/Users/rousan/Downloads/{}.pdf", i))
// // .await
// // .unwrap();
// //
// // while let Some(chunk) = field.next().await {
// // let chunk = chunk.expect("No chunk");
// // file.write_all(&chunk).await.unwrap();
// // }
// //
// // file.flush().await.unwrap();
// //
// // let mut len = 0;
//
// let name = field.name().unwrap();
//
// if name == "my" {
// println!("Name: {}", name);
// let text = field.text().await.unwrap();
// println!("{}", text);
// continue;
// }
//
// if name == "f abc" {
// println!("Name: {}F, Filename: {}", name, field.file_name().unwrap());
// let text = field.text().await.unwrap();
// println!("{}", text);
// continue;
// }
//
// // if name == "file_bin" {
// // println!("Name: {}, Filename: {}", name, field.file_name().unwrap());
// // let bytes = field.bytes().await.unwrap();
// // println!("bytes len: {}", bytes.len());
// // continue;
// // }
//
// // println!("{}", i);
// //
// // let mut len = 0;
// // while let Some(chunk) = field.chunk().await.context("No chunk")? {
// // len += chunk.len();
// // println!("Chunk Size: {}", chunk.len());
// //
// // // if i == 2 {
// // // continue 'outer;
// // // }
// // }
// // println!("Total field data size: {}", len);
// }

Ok(Response::new("Hello, World!".into()))
}

Expand Down
29 changes: 20 additions & 9 deletions src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,9 @@ impl StreamBuffer {
self.stream_size_counter += data.len();

if self.stream_size_counter > self.whole_stream_size_limit {
return Err(crate::Error::new(format!(
"Stream size exceeded the maximum limit: {} bytes",
self.whole_stream_size_limit
)));
return Err(crate::Error::StreamSizeExceeded {
limit: self.whole_stream_size_limit,
});
}

self.buf.extend_from_slice(&data)
Expand All @@ -67,10 +66,16 @@ impl StreamBuffer {
twoway::find_bytes(&self.buf, pattern).map(|idx| self.buf.split_to(idx + pattern.len()).freeze())
}

pub fn read_field_data(&mut self, boundary: &str) -> crate::Result<Option<(bool, Bytes)>> {
pub fn read_field_data(
&mut self,
boundary: &str,
field_name: Option<&str>,
) -> crate::Result<Option<(bool, Bytes)>> {
if self.buf.is_empty() {
return if self.eof {
Err(crate::Error::new("Incomplete field data"))
Err(crate::Error::IncompleteFieldData {
field_name: field_name.map(|s| s.to_owned()),
})
} else {
Ok(None)
};
Expand Down Expand Up @@ -108,7 +113,9 @@ impl StreamBuffer {
let bytes = self.buf.split_to(idx).freeze();

if self.eof {
Err(crate::Error::new("Incomplete field data"))
Err(crate::Error::IncompleteFieldData {
field_name: field_name.map(|s| s.to_owned()),
})
} else {
if bytes.is_empty() {
Ok(None)
Expand All @@ -119,7 +126,9 @@ impl StreamBuffer {
}
None => {
if self.eof {
Err(crate::Error::new("Incomplete field data"))
Err(crate::Error::IncompleteFieldData {
field_name: field_name.map(|s| s.to_owned()),
})
} else {
Ok(Some((false, self.read_full_buf())))
}
Expand All @@ -128,7 +137,9 @@ impl StreamBuffer {
}
None => {
if self.eof {
Err(crate::Error::new("Incomplete field data"))
Err(crate::Error::IncompleteFieldData {
field_name: field_name.map(|s| s.to_owned()),
})
} else {
Ok(Some((false, self.read_full_buf())))
}
Expand Down
136 changes: 82 additions & 54 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,71 +1,99 @@
use derive_more::Display;
use std::fmt::{self, Debug, Display, Formatter};

/// The error type used by the `multer` library.
#[derive(Clone, PartialEq, Eq)]
pub struct Error {
msg: String,
}
type BoxError = Box<dyn std::error::Error + Send + Sync>;

impl Error {
/// Creates a new error instance with the specified message.
pub fn new<M: Into<String>>(msg: M) -> Self {
Error { msg: msg.into() }
}
/// A set of errors that can occur during parsing multipart stream and in other operations.
#[derive(Display)]
#[display(fmt = "multer: {}")]
pub enum Error {
/// An unknown field is detected when multipart [`constraints`](./struct.Constraints.html#method.allowed_fields) are added.
#[display(
fmt = "An unknown field is detected: {}",
"field_name.as_deref().unwrap_or(\"<unknown>\")"
)]
UnknownField { field_name: Option<String> },

/// Converts other error type to the `multer::Error` type.
pub fn wrap<E: std::error::Error + Send + Sync + 'static>(err: E) -> Self {
Error { msg: err.to_string() }
}
}
/// The field data is found incomplete.
#[display(
fmt = "Incomplete field data for field: {}",
"field_name.as_deref().unwrap_or(\"<unknown>\")"
)]
IncompleteFieldData { field_name: Option<String> },

impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "multer: {}", self.msg)
}
}
/// Couldn't read the field headers completely.
#[display(fmt = "Incomplete headers, couldn't read the field headers completely")]
IncompleteHeaders,

impl Debug for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "multer: {}", self.msg)
}
}
/// Failed to read headers.
#[display(fmt = "Failed to read headers")]
ReadHeaderFailed(BoxError),

impl std::error::Error for Error {
fn description(&self) -> &str {
self.msg.as_str()
}
}
/// Failed to decode the field's raw header name to [`HeaderName`](https://docs.rs/http/0.2.1/http/header/struct.HeaderName.html) type.
#[display(fmt = "Failed to decode the field's raw header name: {}", cause)]
DecodeHeaderName { name: String, cause: BoxError },

pub trait ErrorExt {
fn wrap(self) -> Error;
fn context<C: Display + Send + Sync + 'static>(self, ctx: C) -> Error;
}
/// Failed to decode the field's raw header value to [`HeaderValue`](https://docs.rs/http/0.2.1/http/header/struct.HeaderValue.html) type.
#[display(fmt = "Failed to decode the field's raw header value: {}", cause)]
DecodeHeaderValue { value: Vec<u8>, cause: BoxError },

impl<E: std::error::Error + Send + Sync + 'static> ErrorExt for E {
fn wrap(self) -> Error {
Error { msg: self.to_string() }
}
/// Multipart stream is incomplete.
#[display(fmt = "Multipart stream is incomplete")]
IncompleteStream,

fn context<C: Display + Send + Sync + 'static>(self, ctx: C) -> Error {
let msg = format!("{}: {}", ctx, self.to_string());
Error { msg }
}
}
/// The incoming field size exceeded the maximum limit.
#[display(
fmt = "Incoming field size exceeded the maximum limit: {} bytes, field name: {}",
limit,
"field_name.as_deref().unwrap_or(\"<unknown>\")"
)]
FieldSizeExceeded { limit: usize, field_name: Option<String> },

/// The incoming stream size exceeded the maximum limit.
#[display(fmt = "Stream size exceeded the maximum limit: {} bytes", limit)]
StreamSizeExceeded { limit: usize },

/// Stream read failed.
#[display(fmt = "Stream read failed")]
StreamReadFailed(BoxError),

/// Failed to lock the multipart shared state for any changes.
#[display(fmt = "Couldn't lock the multipart state: {}", _0)]
LockFailure(BoxError),

/// The `Content-Type` header is not `multipart/form-data`.
#[display(fmt = "The Content-Type is not multipart/form-data")]
NoMultipart,

/// Failed to convert the `Content-Type` to [`mime::Mime`](https://docs.rs/mime/0.3.16/mime/struct.Mime.html) type.
#[display(fmt = "Failed to convert the Content-Type to `mime::Mime` type: {}", _0)]
DecodeContentType(BoxError),

pub trait ResultExt<T> {
fn wrap(self) -> Result<T, Error>;
fn context<C: Display + Send + Sync + 'static>(self, ctx: C) -> Result<T, Error>;
/// No boundary found in `Content-Type` header.
#[display(fmt = "No boundary found in Content-Type header")]
NoBoundary,

/// Failed to decode the field data as `JSON` in [`field.json()`](./struct.Field.html#method.json) method.
#[cfg(feature = "json")]
#[display(fmt = "Failed to decode the field data as JSON")]
DecodeJson(BoxError),

#[doc(hidden)]
__Nonexhaustive,
}

impl<T, E: std::error::Error + Send + Sync + 'static> ResultExt<T> for Result<T, E> {
fn wrap(self) -> Result<T, Error> {
self.map_err(|e| e.wrap())
impl Debug for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(self, f)
}
}

impl std::error::Error for Error {}

fn context<C: Display + Send + Sync + 'static>(self, ctx: C) -> Result<T, Error> {
match self {
Ok(val) => Ok(val),
Err(err) => Err(err.context(ctx)),
}
impl PartialEq for Error {
fn eq(&self, other: &Self) -> bool {
self.to_string().eq(&other.to_string())
}
}

impl Eq for Error {}
Loading

0 comments on commit d5e168d

Please sign in to comment.