From aee7f3509545bc57305603431edb5fa752fac495 Mon Sep 17 00:00:00 2001 From: Jeb Rosen Date: Mon, 12 Aug 2019 18:38:25 -0700 Subject: [PATCH] Use read_to_string (from futures-preview 0.3.0-alpha.18) to more closely match the pre-async code. --- contrib/lib/Cargo.toml | 2 +- contrib/lib/src/json.rs | 11 +-- core/lib/Cargo.toml | 2 +- core/lib/src/data/from_data.rs | 120 ++++++++++++++++-------------- core/lib/src/request/form/form.rs | 11 ++- 5 files changed, 76 insertions(+), 70 deletions(-) diff --git a/contrib/lib/Cargo.toml b/contrib/lib/Cargo.toml index e4557dc3..4ccf8806 100644 --- a/contrib/lib/Cargo.toml +++ b/contrib/lib/Cargo.toml @@ -42,7 +42,7 @@ memcache_pool = ["databases", "memcache", "r2d2-memcache"] [dependencies] # Global dependencies. -futures-preview = { version = "0.3.0-alpha.17" } +futures-preview = { version = "0.3.0-alpha.18" } rocket_contrib_codegen = { version = "0.5.0-dev", path = "../codegen", optional = true } rocket = { version = "0.5.0-dev", path = "../../core/lib/", default-features = false } log = "0.4" diff --git a/contrib/lib/src/json.rs b/contrib/lib/src/json.rs index e18d29df..b7b291e2 100644 --- a/contrib/lib/src/json.rs +++ b/contrib/lib/src/json.rs @@ -139,15 +139,10 @@ impl<'a, T: Deserialize<'a>> FromData<'a> for Json { fn transform(r: &Request<'_>, d: Data) -> TransformFuture<'a, Self::Owned, Self::Error> { let size_limit = r.limits().get("json").unwrap_or(LIMIT); Box::pin(async move { - let mut v = Vec::with_capacity(512); + let mut s = String::with_capacity(512); let mut reader = d.open().take(size_limit); - match reader.read_to_end(&mut v).await { - Ok(_) => { - match String::from_utf8(v) { - Ok(s) => Borrowed(Success(s)), - Err(e) => Borrowed(Failure((Status::BadRequest, JsonError::Io(std::io::Error::new(std::io::ErrorKind::Other, e))))), - } - }, + match reader.read_to_string(&mut s).await { + Ok(_) => Borrowed(Success(s)), Err(e) => Borrowed(Failure((Status::BadRequest, JsonError::Io(e)))) } }) diff --git a/core/lib/Cargo.toml b/core/lib/Cargo.toml index e62a0ac7..c7c876f8 100644 --- a/core/lib/Cargo.toml +++ b/core/lib/Cargo.toml @@ -26,7 +26,7 @@ private-cookies = ["rocket_http/private-cookies"] [dependencies] rocket_codegen = { version = "0.5.0-dev", path = "../codegen" } rocket_http = { version = "0.5.0-dev", path = "../http" } -futures-preview = { version = "0.3.0-alpha.14", features = ["compat", "io-compat"] } +futures-preview = { version = "0.3.0-alpha.18", features = ["compat", "io-compat"] } tokio = "0.1.16" yansi = "0.5" log = { version = "0.4", features = ["std"] } diff --git a/core/lib/src/data/from_data.rs b/core/lib/src/data/from_data.rs index 03cab1c1..0435586a 100644 --- a/core/lib/src/data/from_data.rs +++ b/core/lib/src/data/from_data.rs @@ -142,7 +142,7 @@ pub type FromDataFuture<'a, T, E> = Pin> + /// if the guard returns successfully. /// /// ```rust -/// # #![feature(proc_macro_hygiene)] +/// # #![feature(proc_macro_hygiene, async_await)] /// # #[macro_use] extern crate rocket; /// # type DataGuard = rocket::data::Data; /// #[post("/submit", data = "")] @@ -188,16 +188,20 @@ pub type FromDataFuture<'a, T, E> = Pin> + /// `String` (an `&str`). /// /// ```rust -/// # #![feature(proc_macro_hygiene)] +/// # #![feature(proc_macro_hygiene, async_await)] /// # #[macro_use] extern crate rocket; /// # #[derive(Debug)] /// # struct Name<'a> { first: &'a str, last: &'a str, } /// use std::io::{self, Read}; /// +/// use futures::io::AsyncReadExt; +/// /// use rocket::{Request, Data, Outcome::*}; -/// use rocket::data::{FromData, Outcome, Transform, Transformed}; +/// use rocket::data::{FromData, Outcome, Transform, Transformed, TransformFuture, FromDataFuture}; /// use rocket::http::Status; /// +/// use rocket::AsyncReadExt as _; +/// /// const NAME_LIMIT: u64 = 256; /// /// enum NameError { @@ -210,32 +214,36 @@ pub type FromDataFuture<'a, T, E> = Pin> + /// type Owned = String; /// type Borrowed = str; /// -/// fn transform(_: &Request, data: Data) -> Transform> { -/// let mut stream = data.open().take(NAME_LIMIT); -/// let mut string = String::with_capacity((NAME_LIMIT / 2) as usize); -/// let outcome = match stream.read_to_string(&mut string) { -/// Ok(_) => Success(string), -/// Err(e) => Failure((Status::InternalServerError, NameError::Io(e))) -/// }; +/// fn transform(_: &Request, data: Data) -> TransformFuture<'a, Self::Owned, Self::Error> { +/// Box::pin(async move { +/// let mut stream = data.open().take(NAME_LIMIT); +/// let mut string = String::with_capacity((NAME_LIMIT / 2) as usize); +/// let outcome = match stream.read_to_string(&mut string).await { +/// Ok(_) => Success(string), +/// Err(e) => Failure((Status::InternalServerError, NameError::Io(e))) +/// }; /// -/// // Returning `Borrowed` here means we get `Borrowed` in `from_data`. -/// Transform::Borrowed(outcome) +/// // Returning `Borrowed` here means we get `Borrowed` in `from_data`. +/// Transform::Borrowed(outcome) +/// }) /// } /// -/// fn from_data(_: &Request, outcome: Transformed<'a, Self>) -> Outcome { -/// // Retrieve a borrow to the now transformed `String` (an &str). This -/// // is only correct because we know we _always_ return a `Borrowed` from -/// // `transform` above. -/// let string = try_outcome!(outcome.borrowed()); +/// fn from_data(_: &Request, outcome: Transformed<'a, Self>) -> FromDataFuture<'a, Self, Self::Error> { +/// Box::pin(async move { +/// // Retrieve a borrow to the now transformed `String` (an &str). This +/// // is only correct because we know we _always_ return a `Borrowed` from +/// // `transform` above. +/// let string = try_outcome!(outcome.borrowed()); /// -/// // Perform a crude, inefficient parse. -/// let splits: Vec<&str> = string.split(" ").collect(); -/// if splits.len() != 2 || splits.iter().any(|s| s.is_empty()) { -/// return Failure((Status::UnprocessableEntity, NameError::Parse)); -/// } +/// // Perform a crude, inefficient parse. +/// let splits: Vec<&str> = string.split(" ").collect(); +/// if splits.len() != 2 || splits.iter().any(|s| s.is_empty()) { +/// return Failure((Status::UnprocessableEntity, NameError::Parse)); +/// } /// -/// // Return successfully. -/// Success(Name { first: splits[0], last: splits[1] }) +/// // Return successfully. +/// Success(Name { first: splits[0], last: splits[1] }) +/// }) /// } /// } /// # #[post("/person", data = "")] @@ -435,7 +443,7 @@ impl<'a> FromData<'a> for Data { /// that you can retrieve it directly from a client's request body: /// /// ```rust -/// # #![feature(proc_macro_hygiene)] +/// # #![feature(proc_macro_hygiene, async_await)] /// # #[macro_use] extern crate rocket; /// # type Person = rocket::data::Data; /// #[post("/person", data = "")] @@ -447,7 +455,7 @@ impl<'a> FromData<'a> for Data { /// A `FromDataSimple` implementation allowing this looks like: /// /// ```rust -/// # #![feature(proc_macro_hygiene)] +/// # #![feature(proc_macro_hygiene, async_await)] /// # #[macro_use] extern crate rocket; /// # /// # #[derive(Debug)] @@ -455,43 +463,50 @@ impl<'a> FromData<'a> for Data { /// # /// use std::io::Read; /// +/// use futures::io::AsyncReadExt; +/// /// use rocket::{Request, Data, Outcome, Outcome::*}; -/// use rocket::data::{self, FromDataSimple}; +/// use rocket::data::{self, FromDataSimple, FromDataFuture}; /// use rocket::http::{Status, ContentType}; /// +/// use rocket::AsyncReadExt as _; +/// /// // Always use a limit to prevent DoS attacks. /// const LIMIT: u64 = 256; /// /// impl FromDataSimple for Person { /// type Error = String; /// -/// fn from_data(req: &Request, data: Data) -> data::Outcome { +/// fn from_data(req: &Request, data: Data) -> FromDataFuture<'static, Self, String> { /// // Ensure the content type is correct before opening the data. /// let person_ct = ContentType::new("application", "x-person"); /// if req.content_type() != Some(&person_ct) { -/// return Outcome::Forward(data); +/// return Box::pin(async move { Outcome::Forward(data) }); /// } /// -/// // Read the data into a String. -/// let mut string = String::new(); -/// if let Err(e) = data.open().take(LIMIT).read_to_string(&mut string) { -/// return Failure((Status::InternalServerError, format!("{:?}", e))); -/// } +/// Box::pin(async move { +/// // Read the data into a String. +/// let mut string = String::new(); +/// let mut reader = data.open().take(LIMIT); +/// if let Err(e) = reader.read_to_string(&mut string).await { +/// return Failure((Status::InternalServerError, format!("{:?}", e))); +/// } /// -/// // Split the string into two pieces at ':'. -/// let (name, age) = match string.find(':') { -/// Some(i) => (string[..i].to_string(), &string[(i + 1)..]), -/// None => return Failure((Status::UnprocessableEntity, "':'".into())) -/// }; +/// // Split the string into two pieces at ':'. +/// let (name, age) = match string.find(':') { +/// Some(i) => (string[..i].to_string(), &string[(i + 1)..]), +/// None => return Failure((Status::UnprocessableEntity, "':'".into())) +/// }; /// -/// // Parse the age. -/// let age: u16 = match age.parse() { -/// Ok(age) => age, -/// Err(_) => return Failure((Status::UnprocessableEntity, "Age".into())) -/// }; +/// // Parse the age. +/// let age: u16 = match age.parse() { +/// Ok(age) => age, +/// Err(_) => return Failure((Status::UnprocessableEntity, "Age".into())) +/// }; /// -/// // Return successfully. -/// Success(Person { name, age }) +/// // Return successfully. +/// Success(Person { name, age }) +/// }) /// } /// } /// # #[post("/person", data = "")] @@ -579,14 +594,11 @@ impl FromDataSimple for String { #[inline(always)] fn from_data(_: &Request<'_>, data: Data) -> FromDataFuture<'static, Self, Self::Error> { Box::pin(async { - let mut stream = data.open(); - let mut buf = Vec::new(); - if let Err(e) = stream.read_to_end(&mut buf).await { - return Failure((Status::BadRequest, e)); - } - match String::from_utf8(buf) { - Ok(s) => Success(s), - Err(e) => Failure((Status::BadRequest, std::io::Error::new(std::io::ErrorKind::Other, e))), + let mut string = String::new(); + let mut reader = data.open(); + match reader.read_to_string(&mut string).await { + Ok(_) => Success(string), + Err(e) => Failure((Status::BadRequest, e)), } }) } diff --git a/core/lib/src/request/form/form.rs b/core/lib/src/request/form/form.rs index a3053a8c..25ca26c1 100644 --- a/core/lib/src/request/form/form.rs +++ b/core/lib/src/request/form/form.rs @@ -196,6 +196,8 @@ impl<'f, T: FromForm<'f> + Send + 'f> FromData<'f> for Form { request: &Request<'_>, data: Data ) -> TransformFuture<'f, Self::Owned, Self::Error> { + use std::cmp::min; + if !request.content_type().map_or(false, |ct| ct.is_form()) { warn_!("Form data does not have form content type."); return Box::pin(futures::future::ready(Transform::Borrowed(Forward(data)))); @@ -204,15 +206,12 @@ impl<'f, T: FromForm<'f> + Send + 'f> FromData<'f> for Form { let limit = request.limits().forms; let mut stream = data.open().take(limit); Box::pin(async move { - let mut buf = Vec::new(); - if let Err(e) = stream.read_to_end(&mut buf).await { + let mut form_string = String::with_capacity(min(4096, limit) as usize); + if let Err(e) = stream.read_to_string(&mut form_string).await { return Transform::Borrowed(Failure((Status::InternalServerError, FormDataError::Io(e)))); } - Transform::Borrowed(match String::from_utf8(buf) { - Ok(s) => Success(s), - Err(e) => Failure((Status::BadRequest, FormDataError::Io(std::io::Error::new(std::io::ErrorKind::Other, e)))), - }) + Transform::Borrowed(Success(form_string)) }) }