diff --git a/contrib/lib/src/json.rs b/contrib/lib/src/json.rs index 265bb758..f220695c 100644 --- a/contrib/lib/src/json.rs +++ b/contrib/lib/src/json.rs @@ -1,14 +1,14 @@ use std::ops::{Deref, DerefMut}; use std::io::{self, Read}; -use rocket::outcome::{Outcome, IntoOutcome}; use rocket::request::Request; -use rocket::data::{self, Data, FromData}; +use rocket::outcome::Outcome::*; +use rocket::data::{Outcome, Transform, Transform::*, Transformed, Data, FromData}; use rocket::response::{self, Responder, content}; use rocket::http::Status; use serde::{Serialize, Serializer}; -use serde::de::{Deserialize, DeserializeOwned, Deserializer}; +use serde::de::{Deserialize, Deserializer}; use serde_json; /// The JSON type: implements `FromData` and `Responder`, allowing you to easily @@ -19,20 +19,20 @@ use serde_json; /// If you're receiving JSON data, simply add a `data` parameter to your route /// arguments and ensure the type of the parameter is a `Json`, where `T` is /// some type you'd like to parse from JSON. `T` must implement `Deserialize` or -/// `DeserializeOwned` from [Serde](https://github.com/serde-rs/json). The data -/// is parsed from the HTTP request body. +/// `DeserializeOwned` from [`serde`](https://github.com/serde-rs/json). The +/// data is parsed from the HTTP request body. /// /// ```rust,ignore -/// #[post("/users/", format = "application/json", data = "")] +/// #[post("/users/", format = "json", data = "")] /// fn new_user(user: Json) { /// ... /// } /// ``` /// -/// You don't _need_ to use `format = "application/json"`, but it _may_ be what -/// you want. Using `format = application/json` means that any request that -/// doesn't specify "application/json" as its `Content-Type` header value will -/// not be routed to the handler. +/// You don't _need_ to use `format = "json"`, but it _may_ be what you want. +/// Using `format = json` means that any request that doesn't specify +/// "application/json" as its `Content-Type` header value will not be routed to +/// the handler. /// /// ## Sending JSON /// @@ -52,7 +52,7 @@ use serde_json; /// ## Incoming Data Limits /// /// The default size limit for incoming JSON data is 1MiB. Setting a limit -/// protects your application from denial of service (DOS) attacks and from +/// protects your application from denial of service (DoS) attacks and from /// resource exhaustion through high memory consumption. The limit can be /// increased by setting the `limits.json` configuration parameter. For /// instance, to increase the JSON limit to 5MiB for all environments, you may @@ -81,49 +81,50 @@ impl Json { } } -/// Like [`from_reader`] but eagerly reads the content of the reader to a string -/// and delegates to `from_str`. -/// -/// [`from_reader`]: https://docs.serde.rs/serde_json/fn.from_reader.html -fn from_reader_eager(mut reader: R) -> Result - where R: Read, T: DeserializeOwned -{ - let mut s = String::with_capacity(512); - reader.read_to_string(&mut s).map_err(JsonError::Io)?; - - serde_json::from_str(&s).map_err(|e| JsonError::Parse(s, e)) -} - /// Default limit for JSON is 1MB. const LIMIT: u64 = 1 << 20; /// An error returned by the [`Json`] data guard when incoming data fails to /// serialize as JSON. #[derive(Debug)] -pub enum JsonError { +pub enum JsonError<'a> { /// An I/O error occurred while reading the incoming request data. Io(io::Error), + /// The client's data was received successfully but failed to parse as valid - /// JSON or as the requested type. The `String` value in `.0` is the raw - /// data received from the user, while the `Error` in `.1` is the - /// deserialization error from `serde`. - Parse(String, serde_json::error::Error), + /// JSON or as the requested type. The `&str` value in `.0` is the raw data + /// received from the user, while the `Error` in `.1` is the deserialization + /// error from `serde`. + Parse(&'a str, serde_json::error::Error), } -impl FromData for Json { - type Error = JsonError; +impl<'a, T: Deserialize<'a>> FromData<'a> for Json { + type Error = JsonError<'a>; + type Owned = String; + type Borrowed = str; - fn from_data(request: &Request, data: Data) -> data::Outcome { - if !request.content_type().map_or(false, |ct| ct.is_json()) { - error_!("Content-Type is not JSON."); - return Outcome::Forward(data); + fn transform(r: &Request, d: Data) -> Transform> { + let size_limit = r.limits().get("json").unwrap_or(LIMIT); + let mut s = String::with_capacity(512); + match d.open().take(size_limit).read_to_string(&mut s) { + Ok(_) => Borrowed(Success(s)), + Err(e) => Borrowed(Failure((Status::BadRequest, JsonError::Io(e)))) } + } - let size_limit = request.limits().get("json").unwrap_or(LIMIT); - from_reader_eager(data.open().take(size_limit)) - .map(Json) - .map_err(|e| { error_!("Couldn't parse JSON body: {:?}", e); e }) - .into_outcome(Status::BadRequest) + fn from_data(_: &Request, o: Transformed<'a, Self>) -> Outcome { + let string = o.borrowed()?; + match serde_json::from_str(&string) { + Ok(v) => Success(Json(v)), + Err(e) => { + error_!("Couldn't parse JSON body: {:?}", e); + if e.is_data() { + Failure((Status::UnprocessableEntity, JsonError::Parse(string, e))) + } else { + Failure((Status::BadRequest, JsonError::Parse(string, e))) + } + } + } } } diff --git a/contrib/lib/src/msgpack.rs b/contrib/lib/src/msgpack.rs index 55163d9f..daa68eca 100644 --- a/contrib/lib/src/msgpack.rs +++ b/contrib/lib/src/msgpack.rs @@ -3,14 +3,14 @@ extern crate rmp_serde; use std::ops::{Deref, DerefMut}; use std::io::{Cursor, Read}; -use rocket::outcome::{Outcome, IntoOutcome}; use rocket::request::Request; -use rocket::data::{self, Data, FromData}; +use rocket::outcome::Outcome::*; +use rocket::data::{Outcome, Transform, Transform::*, Transformed, Data, FromData}; use rocket::response::{self, Responder, Response}; -use rocket::http::{ContentType, Status}; +use rocket::http::Status; use serde::Serialize; -use serde::de::DeserializeOwned; +use serde::de::Deserialize; pub use self::rmp_serde::decode::Error as MsgPackError; @@ -23,8 +23,8 @@ pub use self::rmp_serde::decode::Error as MsgPackError; /// route arguments and ensure the type of the parameter is a `MsgPack`, /// where `T` is some type you'd like to parse from MessagePack. `T` must /// implement `Deserialize` or `DeserializeOwned` from -/// [Serde](https://github.com/serde-rs/serde). The data is parsed from the HTTP -/// request body. +/// [`serde`](https://github.com/serde-rs/serde). The data is parsed from the +/// HTTP request body. /// /// ```rust /// # #![feature(plugin, decl_macro)] @@ -45,9 +45,7 @@ pub use self::rmp_serde::decode::Error as MsgPackError; /// You don't _need_ to use `format = "msgpack"`, but it _may_ be what you want. /// Using `format = msgpack` means that any request that doesn't specify /// "application/msgpack" as its first `Content-Type:` header parameter will not -/// be routed to this handler. By default, Rocket will accept a Content-Type of -/// any of the following for MessagePack data: `application/msgpack`, -/// `application/x-msgpack`, `bin/msgpack`, or `bin/x-msgpack`. +/// be routed to this handler. /// /// ## Sending MessagePack /// @@ -110,34 +108,36 @@ impl MsgPack { /// Default limit for MessagePack is 1MB. const LIMIT: u64 = 1 << 20; -/// Accepted content types are: `application/msgpack`, `application/x-msgpack`, -/// `bin/msgpack`, and `bin/x-msgpack`. -#[inline(always)] -fn is_msgpack_content_type(ct: &ContentType) -> bool { - (ct.top() == "application" || ct.top() == "bin") - && (ct.sub() == "msgpack" || ct.sub() == "x-msgpack") -} - -impl FromData for MsgPack { +impl<'a, T: Deserialize<'a>> FromData<'a> for MsgPack { type Error = MsgPackError; + type Owned = Vec; + type Borrowed = [u8]; - fn from_data(request: &Request, data: Data) -> data::Outcome { - if !request.content_type().map_or(false, |ct| is_msgpack_content_type(&ct)) { - error_!("Content-Type is not MessagePack."); - return Outcome::Forward(data); - } - + fn transform(r: &Request, d: Data) -> Transform> { let mut buf = Vec::new(); - let size_limit = request.limits().get("msgpack").unwrap_or(LIMIT); - if let Err(e) = data.open().take(size_limit).read_to_end(&mut buf) { - let e = MsgPackError::InvalidDataRead(e); - error_!("Couldn't read request data: {:?}", e); - return Outcome::Failure((Status::BadRequest, e)); - }; + let size_limit = r.limits().get("msgpack").unwrap_or(LIMIT); + match d.open().take(size_limit).read_to_end(&mut buf) { + Ok(_) => Borrowed(Success(buf)), + Err(e) => Borrowed(Failure((Status::BadRequest, MsgPackError::InvalidDataRead(e)))) + } + } - rmp_serde::from_slice(&buf).map(MsgPack) - .map_err(|e| { error_!("Couldn't parse MessagePack body: {:?}", e); e }) - .into_outcome(Status::BadRequest) + fn from_data(_: &Request, o: Transformed<'a, Self>) -> Outcome { + use self::MsgPackError::*; + + let buf = o.borrowed()?; + match rmp_serde::from_slice(&buf) { + Ok(val) => Success(MsgPack(val)), + Err(e) => { + error_!("Couldn't parse MessagePack body: {:?}", e); + match e { + TypeMismatch(_) | OutOfRange | LengthMismatch(_) => { + Failure((Status::UnprocessableEntity, e)) + } + _ => Failure((Status::BadRequest, e)) + } + } + } } } diff --git a/core/codegen/src/decorators/route.rs b/core/codegen/src/decorators/route.rs index e1b45382..5e6d0663 100644 --- a/core/codegen/src/decorators/route.rs +++ b/core/codegen/src/decorators/route.rs @@ -86,7 +86,7 @@ impl RouteParams { ).expect("form statement")) } - fn generate_data_statement(&self, ecx: &ExtCtxt) -> Option { + fn generate_data_statements(&self, ecx: &ExtCtxt) -> Option<(Stmt, Stmt)> { let param = self.data_param.as_ref().map(|p| &p.value)?; let arg = self.annotated_fn.find_input(¶m.node.name); if arg.is_none() { @@ -97,18 +97,43 @@ impl RouteParams { let arg = arg.unwrap(); let name = arg.ident().expect("form param identifier").prepend(PARAM_PREFIX); let ty = strip_ty_lifetimes(arg.ty.clone()); - Some(quote_stmt!(ecx, + + let transform_stmt = quote_stmt!(ecx, + let __transform = <$ty as ::rocket::data::FromData>::transform(__req, __data); + ).expect("data statement"); + + let data_stmt = quote_stmt!(ecx, #[allow(non_snake_case, unreachable_patterns)] - let $name: $ty = - match ::rocket::data::FromData::from_data(__req, __data) { + let $name: $ty = { + let __outcome = match __transform { + ::rocket::data::Transform::Owned(::rocket::Outcome::Success(__v)) => { + ::rocket::data::Transform::Owned(::rocket::Outcome::Success(__v)) + } + ::rocket::data::Transform::Borrowed(::rocket::Outcome::Success(ref v)) => { + let borrow = ::std::borrow::Borrow::borrow(v); + ::rocket::data::Transform::Borrowed(::rocket::Outcome::Success(borrow)) + } + ::rocket::data::Transform::Owned(inner) => { + ::rocket::data::Transform::Owned(inner) + } + ::rocket::data::Transform::Borrowed(inner) => { + ::rocket::data::Transform::Borrowed(inner.map(|_| unreachable!())) + } + }; + + match <$ty as ::rocket::data::FromData>::from_data(__req, __outcome) { ::rocket::Outcome::Success(d) => d, - ::rocket::Outcome::Forward(d) => - return ::rocket::Outcome::Forward(d), + ::rocket::Outcome::Forward(d) => { + return ::rocket::Outcome::Forward(d); + } ::rocket::Outcome::Failure((code, _)) => { return ::rocket::Outcome::Failure(code); } - }; - ).expect("data statement")) + } + }; + ).expect("data statement"); + + Some((transform_stmt, data_stmt)) } fn generate_query_statement(&self, ecx: &ExtCtxt) -> Option { @@ -285,9 +310,11 @@ fn generic_route_decorator(known_method: Option>, let param_statements = route.generate_param_statements(ecx); let query_statement = route.generate_query_statement(ecx); - let data_statement = route.generate_data_statement(ecx); let fn_arguments = route.generate_fn_arguments(ecx); let uri_macro = route.generate_uri_macro(ecx); + let (transform_statement, data_statement) = route.generate_data_statements(ecx) + .map(|(a, b)| (Some(a), Some(b))) + .unwrap_or((None, None)); // Generate and emit the wrapping function with the Rocket handler signature. let user_fn_name = route.annotated_fn.ident(); @@ -300,6 +327,7 @@ fn generic_route_decorator(known_method: Option>, -> ::rocket::handler::Outcome<'_b> { $param_statements $query_statement + $transform_statement $data_statement let responder = $user_fn_name($fn_arguments); ::rocket::handler::Outcome::from(__req, responder) diff --git a/core/codegen/tests/complete-decorator.rs b/core/codegen/tests/complete-decorator.rs index f54a5eb0..86daeb58 100644 --- a/core/codegen/tests/complete-decorator.rs +++ b/core/codegen/tests/complete-decorator.rs @@ -13,13 +13,13 @@ struct User<'a> { } #[post("/<_name>?<_query>", format = "application/json", data = "", rank = 2)] -fn get<'r>( +fn get( _name: &RawStr, - _query: User<'r>, - user: Form<'r, User<'r>>, + _query: User, + user: Form, _cookies: Cookies ) -> String { - format!("{}:{}", user.get().name, user.get().nickname) + format!("{}:{}", user.name, user.nickname) } #[test] diff --git a/core/codegen/tests/typed-uris.rs b/core/codegen/tests/typed-uris.rs index aab3be18..232808cf 100644 --- a/core/codegen/tests/typed-uris.rs +++ b/core/codegen/tests/typed-uris.rs @@ -71,7 +71,7 @@ fn no_uri_display_okay(id: i32, form: Form) -> &'static str { fn complex<'r>( name: &RawStr, query: User<'r>, - user: Form<'r, User<'r>>, + user: Form>, cookies: Cookies ) -> &'static str { "" } diff --git a/core/codegen_next/src/derive/from_form.rs b/core/codegen_next/src/derive/from_form.rs index 4df96c12..4992c572 100644 --- a/core/codegen_next/src/derive/from_form.rs +++ b/core/codegen_next/src/derive/from_form.rs @@ -58,7 +58,7 @@ fn validate_struct(gen: &DeriveGenerator, data: Struct) -> Result<()> { } pub fn derive_from_form(input: TokenStream) -> TokenStream { - let form_error = quote!(::rocket::request::FormError); + let form_error = quote!(::rocket::request::FormParseError); DeriveGenerator::build_for(input, "::rocket::request::FromForm<'__f>") .generic_support(GenericSupport::Lifetime | GenericSupport::Type) .replace_generic(0, 0) @@ -72,7 +72,7 @@ pub fn derive_from_form(input: TokenStream) -> TokenStream { }) .validate_struct(validate_struct) .function(|_, inner| quote! { - type Error = ::rocket::request::FormError<'__f>; + type Error = ::rocket::request::FormParseError<'__f>; fn from_form( __items: &mut ::rocket::request::FormItems<'__f>, diff --git a/core/codegen_next/tests/from_form.rs b/core/codegen_next/tests/from_form.rs index 2516045e..72c7d156 100644 --- a/core/codegen_next/tests/from_form.rs +++ b/core/codegen_next/tests/from_form.rs @@ -1,10 +1,10 @@ #[macro_use] extern crate rocket; -use rocket::request::{FromForm, FormItems, FormError}; +use rocket::request::{FromForm, FormItems, FormParseError}; use rocket::http::RawStr; -fn parse<'f, T>(string: &'f str, strict: bool) -> Result> - where T: FromForm<'f, Error = FormError<'f>> +fn parse<'f, T>(string: &'f str, strict: bool) -> Result> + where T: FromForm<'f, Error = FormParseError<'f>> { let mut items = FormItems::from(string); let result = T::from_form(items.by_ref(), strict); @@ -15,14 +15,14 @@ fn parse<'f, T>(string: &'f str, strict: bool) -> Result> result } -fn strict<'f, T>(string: &'f str) -> Result> - where T: FromForm<'f, Error = FormError<'f>> +fn strict<'f, T>(string: &'f str) -> Result> + where T: FromForm<'f, Error = FormParseError<'f>> { parse(string, true) } -fn lenient<'f, T>(string: &'f str) -> Result> - where T: FromForm<'f, Error = FormError<'f>> +fn lenient<'f, T>(string: &'f str) -> Result> + where T: FromForm<'f, Error = FormParseError<'f>> { parse(string, false) } @@ -297,23 +297,23 @@ fn form_errors() { assert_eq!(form, Ok(WhoopsForm { complete: true, other: 781 })); let form: Result = strict("complete=true&other=unknown"); - assert_eq!(form, Err(FormError::BadValue("other".into(), "unknown".into()))); + assert_eq!(form, Err(FormParseError::BadValue("other".into(), "unknown".into()))); let form: Result = strict("complete=unknown&other=unknown"); - assert_eq!(form, Err(FormError::BadValue("complete".into(), "unknown".into()))); + assert_eq!(form, Err(FormParseError::BadValue("complete".into(), "unknown".into()))); let form: Result = strict("complete=true&other=1&extra=foo"); - assert_eq!(form, Err(FormError::Unknown("extra".into(), "foo".into()))); + assert_eq!(form, Err(FormParseError::Unknown("extra".into(), "foo".into()))); // Bad values take highest precedence. let form: Result = strict("complete=unknown&unknown=foo"); - assert_eq!(form, Err(FormError::BadValue("complete".into(), "unknown".into()))); + assert_eq!(form, Err(FormParseError::BadValue("complete".into(), "unknown".into()))); // Then unknown key/values for strict parses. let form: Result = strict("complete=true&unknown=foo"); - assert_eq!(form, Err(FormError::Unknown("unknown".into(), "foo".into()))); + assert_eq!(form, Err(FormParseError::Unknown("unknown".into(), "foo".into()))); // Finally, missing. let form: Result = strict("complete=true"); - assert_eq!(form, Err(FormError::Missing("other".into()))); + assert_eq!(form, Err(FormParseError::Missing("other".into()))); } diff --git a/core/lib/src/data/data.rs b/core/lib/src/data/data.rs index 37c37484..344f50bd 100644 --- a/core/lib/src/data/data.rs +++ b/core/lib/src/data/data.rs @@ -27,12 +27,17 @@ const PEEK_BYTES: usize = 512; /// /// This type is the only means by which the body of a request can be retrieved. /// This type is not usually used directly. Instead, types that implement -/// [FromData](/rocket/data/trait.FromData.html) are used via code generation by -/// specifying the `data = ""` route parameter as follows: +/// [`FromData`] are used via code generation by specifying the `data = ""` +/// route parameter as follows: /// -/// ```rust,ignore +/// ```rust +/// # #![feature(plugin, decl_macro)] +/// # #![plugin(rocket_codegen)] +/// # #[macro_use] extern crate rocket; +/// # type DataGuard = ::rocket::data::Data; /// #[post("/submit", data = "")] -/// fn submit(var: T) -> ... { ... } +/// fn submit(var: DataGuard) { /* ... */ } +/// # fn main() { } /// ``` /// /// Above, `T` can be any type that implements `FromData`. Note that `Data` diff --git a/core/lib/src/data/from_data.rs b/core/lib/src/data/from_data.rs index 363c6bc7..c5cdb75f 100644 --- a/core/lib/src/data/from_data.rs +++ b/core/lib/src/data/from_data.rs @@ -1,4 +1,5 @@ use std::io::{self, Read}; +use std::borrow::Borrow; use outcome::{self, IntoOutcome}; use outcome::Outcome::*; @@ -30,6 +31,84 @@ impl<'a, S, E> IntoOutcome for Result { } } +/// Indicates how incoming data should be transformed before being parsed and +/// validated by a data guard. +/// +/// See the documentation for [`FromData`] for usage details. +pub enum Transform { + /// Indicates that data should be or has been transformed into the + /// [`FromData::Owned`] variant. + Owned(T), + + /// Indicates that data should be or has been transformed into the + /// [`FromData::Borrowed`] variant. + Borrowed(B) +} + +impl Transform { + /// Returns the `Owned` value if `self` is `Owned`. + /// + /// # Panics + /// + /// Panics if `self` is `Borrowed`. + /// + /// + /// # Example + /// + /// ```rust + /// use rocket::data::Transform; + /// + /// let owned: Transform = Transform::Owned(10); + /// assert_eq!(owned.owned(), 10); + /// ``` + #[inline] + pub fn owned(self) -> T { + match self { + Transform::Owned(val) => val, + Transform::Borrowed(_) => panic!("Transform::owned() called on Borrowed"), + } + } + + /// Returns the `Borrowed` value if `self` is `Borrowed`. + /// + /// # Panics + /// + /// Panics if `self` is `Owned`. + /// + /// ```rust + /// use rocket::data::Transform; + /// + /// let borrowed: Transform = Transform::Borrowed(&[10]); + /// assert_eq!(borrowed.borrowed(), &[10]); + /// ``` + #[inline] + pub fn borrowed(self) -> B { + match self { + Transform::Borrowed(val) => val, + Transform::Owned(_) => panic!("Transform::borrowed() called on Owned"), + } + } +} + +/// Type alias to the `outcome` input type of [`FromData::from_data`]. +/// +/// This is a hairy type, but the gist is that this is a [`Transform`] where, +/// for a given `T: FromData`: +/// +/// * The `Owned` variant is an `Outcome` whose `Success` value is of type +/// [`FromData::Owned`]. +/// +/// * The `Borrowed` variant is an `Outcome` whose `Success` value is a borrow +/// of type [`FromData::Borrowed`]. +/// +/// * In either case, the `Outcome`'s `Failure` variant is a value of type +/// [`FromData::Error`]. +pub type Transformed<'a, T> = + Transform< + Outcome<>::Owned, >::Error>, + Outcome<&'a >::Borrowed, >::Error> + >; + /// Trait implemented by data guards to derive a value from request body data. /// /// # Data Guards @@ -39,21 +118,122 @@ impl<'a, S, E> IntoOutcome for Result { /// Validation and parsing/conversion is implemented through `FromData`. In /// other words, every type that implements `FromData` is a data guard. /// -/// [request guard]: /rocket/request/trait.FromRequest.html -/// /// Data guards are used as the target of the `data` route attribute parameter. /// A handler can have at most one data guard. /// +/// [request guard]: /rocket/request/trait.FromRequest.html +/// /// ## Example /// /// In the example below, `var` is used as the argument name for the data guard -/// type `T`. When the `submit` route matches, Rocket will call the `FromData` -/// implementation for the type `T`. The handler will only be called if the guard -/// returns successfully. +/// type `DataGuard`. When the `submit` route matches, Rocket will call the +/// `FromData` implementation for the type `T`. The handler will only be called +/// if the guard returns successfully. /// -/// ```rust,ignore +/// ```rust +/// # #![feature(plugin, decl_macro)] +/// # #![plugin(rocket_codegen)] +/// # #[macro_use] extern crate rocket; +/// # type DataGuard = ::rocket::data::Data; /// #[post("/submit", data = "")] -/// fn submit(var: T) -> ... { ... } +/// fn submit(var: DataGuard) { /* ... */ } +/// # fn main() { } +/// ``` +/// +/// # Transforming +/// +/// Data guards can optionally _transform_ incoming data before processing it +/// via an implementation of the [`FromData::transform()`] method. This is +/// useful when a data guard requires or could benefit from a reference to body +/// data as opposed to an owned version. If a data guard has no need to operate +/// on a reference to body data, [`FromDataSimple`] should be implemented +/// instead; it is simpler to implement and less error prone. All types that +/// implement `FromDataSimple` automatically implement `FromData`. +/// +/// When exercising a data guard, Rocket first calls the guard's +/// [`FromData::transform()`] method and then subsequently calls the guard's +/// [`FromData::from_data()`] method. Rocket stores data returned by +/// [`FromData::transform()`] on the stack. If `transform` returns a +/// [`Transform::Owned`], Rocket moves the data back to the data guard in the +/// subsequent `from_data` call as a `Transform::Owned`. If instead `transform` +/// returns a [`Transform::Borrowed`] variant, Rocket calls `borrow()` on the +/// owned value, producing a borrow of the associated [`FromData::Borrowed`] +/// type and passing it as a `Transform::Borrowed`. +/// +/// ## Example +/// +/// Consider a data guard type that wishes to hold a slice to two different +/// parts of the incoming data: +/// +/// ```rust +/// struct Name<'a> { +/// first: &'a str, +/// last: &'a str +/// } +/// ``` +/// +/// Without the ability to transform into a borrow, implementing such a data +/// guard would be impossible. With transformation, however, we can instruct +/// Rocket to produce a borrow to a `Data` that has been transformed into a +/// `String` (an `&str`). +/// +/// ```rust +/// # #![feature(plugin, decl_macro)] +/// # #![plugin(rocket_codegen)] +/// # extern crate rocket; +/// # #[derive(Debug)] +/// # struct Name<'a> { first: &'a str, last: &'a str, } +/// use std::io::{self, Read}; +/// +/// use rocket::{Request, Data, Outcome::*}; +/// use rocket::data::{FromData, Outcome, Transform, Transformed}; +/// use rocket::http::Status; +/// +/// const NAME_LIMIT: u64 = 256; +/// +/// enum NameError { +/// Io(io::Error), +/// Parse +/// } +/// +/// impl<'a> FromData<'a> for Name<'a> { +/// type Error = NameError; +/// 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))) +/// }; +/// +/// // 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 = 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)); +/// } +/// +/// // Return successfully. +/// Success(Name { first: splits[0], last: splits[1] }) +/// } +/// } +/// # #[post("/person", data = "")] +/// # fn person(person: Name) { } +/// # #[post("/person", data = "")] +/// # fn person2(person: Result) { } +/// # fn main() { } /// ``` /// /// # Outcomes @@ -127,6 +307,100 @@ impl<'a, S, E> IntoOutcome for Result { /// memory; since the user controls the size of the body, this is an obvious /// vector for a denial of service attack. /// +/// # Simple `FromData` +/// +/// For an example of a type that wouldn't require transformation, see the +/// [`FromDataSimple`] documentation. +pub trait FromData<'a>: Sized { + /// The associated error to be returned when the guard fails. + type Error; + + /// The owned type returned from [`FromData::transform()`]. + /// + /// The trait bounds ensures that it is is possible to borrow an + /// `&Self::Borrowed` from a value of this type. + type Owned: Borrow; + + /// The _borrowed_ type consumed by [`FromData::from_data()`] when + /// [`FromData::transform()`] returns a [`Transform::Borrowed`]. + /// + /// If [`FromData::from_data()`] returns a [`Transform::Owned`], this + /// associated type should be set to `Self::Owned`. + type Borrowed: ?Sized; + + /// Transforms `data` into a value of type `Self::Owned`. + /// + /// If this method returns a `Transform::Owned(Self::Owned)`, then + /// `from_data` should subsequently be called with a `data` value of + /// `Transform::Owned(Self::Owned)`. If this method returns a + /// `Transform::Borrowed(Self::Owned)`, `from_data` should subsequently be + /// called with a `data` value of `Transform::Borrowed(&Self::Borrowed)`. In + /// other words, the variant of `Transform` returned from this method is + /// used to determine which variant of `Transform` should be passed to the + /// `from_data` method. Rocket _always_ makes the subsequent call correctly. + /// + /// It is very unlikely that a correct implementation of this method is + /// capable of returning either of an `Owned` or `Borrowed` variant. + /// Instead, this method should return exactly _one_ of these variants. + /// + /// If transformation succeeds, an outcome of `Success` is returned. + /// If the data is not appropriate given the type of `Self`, `Forward` is + /// returned. On failure, `Failure` is returned. + fn transform(request: &Request, data: Data) -> Transform>; + + /// Validates, parses, and converts the incoming request body data into an + /// instance of `Self`. + /// + /// If validation and parsing succeeds, an outcome of `Success` is returned. + /// If the data is not appropriate given the type of `Self`, `Forward` is + /// returned. If parsing or validation fails, `Failure` is returned. + /// + /// # Example + /// + /// When implementing this method, you rarely need to destruct the `outcome` + /// parameter. Instead, the first line of the method should be one of the + /// following: + /// + /// ```rust + /// # use rocket::data::{Data, FromData, Transformed, Outcome}; + /// # fn f<'a>(outcome: Transformed<'a, Data>) -> Outcome>::Error> { + /// // If `Owned` was returned from `transform`: + /// let data = outcome.owned()?; + /// # unimplemented!() + /// # } + /// + /// # fn g<'a>(outcome: Transformed<'a, Data>) -> Outcome>::Error> { + /// // If `Borrowed` was returned from `transform`: + /// let data = outcome.borrowed()?; + /// # unimplemented!() + /// # } + /// ``` + fn from_data(request: &Request, outcome: Transformed<'a, Self>) -> Outcome; +} + +/// The identity implementation of `FromData`. Always returns `Success`. +impl<'f> FromData<'f> for Data { + type Error = !; + type Owned = Data; + type Borrowed = Data; + + #[inline(always)] + fn transform(_: &Request, data: Data) -> Transform> { + Transform::Owned(Success(data)) + } + + #[inline(always)] + fn from_data(_: &Request, outcome: Transformed<'f, Self>) -> Outcome { + Success(outcome.owned()?) + } +} + +/// A simple, less complex variant of [`FromData`]. +/// +/// When transformation of incoming data isn't required, data guards should +/// implement this trait instead of [`FromData`]. For a description of data +/// guards, see the [`FromData`] documentation. +/// /// # Example /// /// Say that you have a custom type, `Person`: @@ -144,16 +418,22 @@ impl<'a, S, E> IntoOutcome for Result { /// application/x-person`. You'd like to use `Person` as a `FromData` type so /// that you can retrieve it directly from a client's request body: /// -/// ```rust,ignore +/// ```rust +/// # #![feature(plugin, decl_macro)] +/// # #![plugin(rocket_codegen)] +/// # #[macro_use] extern crate rocket; +/// # type Person = ::rocket::data::Data; /// #[post("/person", data = "")] /// fn person(person: Person) -> &'static str { /// "Saved the new person to the database!" /// } /// ``` /// -/// A `FromData` implementation allowing this looks like: +/// A `FromDataSimple` implementation allowing this looks like: /// /// ```rust +/// # #![allow(unused_attributes)] +/// # #![allow(unused_variables)] /// # #![feature(plugin, decl_macro)] /// # #![plugin(rocket_codegen)] /// # extern crate rocket; @@ -162,12 +442,15 @@ impl<'a, S, E> IntoOutcome for Result { /// # struct Person { name: String, age: u16 } /// # /// use std::io::Read; -/// use rocket::{Request, Data, Outcome}; -/// use rocket::data::{self, FromData}; -/// use rocket::http::{Status, ContentType}; -/// use rocket::Outcome::*; /// -/// impl FromData for Person { +/// use rocket::{Request, Data, Outcome, Outcome::*}; +/// use rocket::data::{self, FromDataSimple}; +/// use rocket::http::{Status, ContentType}; +/// +/// // 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 { @@ -179,13 +462,13 @@ impl<'a, S, E> IntoOutcome for Result { /// /// // Read the data into a String. /// let mut string = String::new(); -/// if let Err(e) = data.open().read_to_string(&mut string) { +/// if let Err(e) = data.open().take(LIMIT).read_to_string(&mut string) { /// return Failure((Status::InternalServerError, format!("{:?}", e))); /// } /// /// // Split the string into two pieces at ':'. /// let (name, age) = match string.find(':') { -/// Some(i) => (&string[..i], &string[(i + 1)..]), +/// Some(i) => (string[..i].to_string(), &string[(i + 1)..]), /// None => return Failure((Status::UnprocessableEntity, "':'".into())) /// }; /// @@ -196,20 +479,16 @@ impl<'a, S, E> IntoOutcome for Result { /// }; /// /// // Return successfully. -/// Success(Person { -/// name: name.into(), -/// age: age -/// }) +/// Success(Person { name, age }) /// } /// } -/// # /// # #[post("/person", data = "")] /// # fn person(person: Person) { } /// # #[post("/person", data = "")] /// # fn person2(person: Result) { } /// # fn main() { } /// ``` -pub trait FromData: Sized { +pub trait FromDataSimple: Sized { /// The associated error to be returned when the guard fails. type Error; @@ -222,41 +501,66 @@ pub trait FromData: Sized { fn from_data(request: &Request, data: Data) -> Outcome; } -/// The identity implementation of `FromData`. Always returns `Success`. -impl FromData for Data { - type Error = !; +impl<'a, T: FromDataSimple> FromData<'a> for T { + type Error = T::Error; + type Owned = Data; + type Borrowed = Data; - fn from_data(_: &Request, data: Data) -> Outcome { - Success(data) + #[inline(always)] + fn transform(_: &Request, d: Data) -> Transform> { + Transform::Owned(Success(d)) + } + + #[inline(always)] + fn from_data(req: &Request, o: Transformed<'a, Self>) -> Outcome { + T::from_data(req, o.owned()?) } } -impl FromData for Result { - type Error = !; +impl<'a, T: FromData<'a> + 'a> FromData<'a> for Result { + type Error = T::Error; + type Owned = T::Owned; + type Borrowed = T::Borrowed; - fn from_data(request: &Request, data: Data) -> Outcome { - match T::from_data(request, data) { + #[inline(always)] + fn transform(r: &Request, d: Data) -> Transform> { + T::transform(r, d) + } + + #[inline(always)] + fn from_data(r: &Request, o: Transformed<'a, Self>) -> Outcome { + match T::from_data(r, o) { Success(val) => Success(Ok(val)), - Failure((_, val)) => Success(Err(val)), Forward(data) => Forward(data), + Failure((_, e)) => Success(Err(e)), } } } -impl FromData for Option { - type Error = !; +impl<'a, T: FromData<'a> + 'a> FromData<'a> for Option { + type Error = T::Error; + type Owned = T::Owned; + type Borrowed = T::Borrowed; - fn from_data(request: &Request, data: Data) -> Outcome { - match T::from_data(request, data) { + #[inline(always)] + fn transform(r: &Request, d: Data) -> Transform> { + T::transform(r, d) + } + + #[inline(always)] + fn from_data(r: &Request, o: Transformed<'a, Self>) -> Outcome { + match T::from_data(r, o) { Success(val) => Success(Some(val)), Failure(_) | Forward(_) => Success(None), } } } -impl FromData for String { +#[cfg(debug_assertions)] +impl FromDataSimple for String { type Error = io::Error; + #[inline(always)] fn from_data(_: &Request, data: Data) -> Outcome { let mut string = String::new(); match data.open().read_to_string(&mut string) { @@ -266,9 +570,11 @@ impl FromData for String { } } -impl FromData for Vec { +#[cfg(debug_assertions)] +impl FromDataSimple for Vec { type Error = io::Error; + #[inline(always)] fn from_data(_: &Request, data: Data) -> Outcome { let mut bytes = Vec::new(); match data.open().read_to_end(&mut bytes) { diff --git a/core/lib/src/data/mod.rs b/core/lib/src/data/mod.rs index 15893b83..20523fac 100644 --- a/core/lib/src/data/mod.rs +++ b/core/lib/src/data/mod.rs @@ -7,4 +7,4 @@ mod from_data; pub use self::data::Data; pub use self::data_stream::DataStream; -pub use self::from_data::{FromData, Outcome}; +pub use self::from_data::{FromData, FromDataSimple, Outcome, Transform, Transformed}; diff --git a/core/lib/src/lib.rs b/core/lib/src/lib.rs index da7681b3..23a6df02 100644 --- a/core/lib/src/lib.rs +++ b/core/lib/src/lib.rs @@ -6,6 +6,7 @@ #![feature(proc_macro_non_items)] #![feature(crate_visibility_modifier)] #![feature(try_from)] +#![feature(label_break_value)] #![recursion_limit="256"] diff --git a/core/lib/src/request/form/error.rs b/core/lib/src/request/form/error.rs index 1308a687..18df0d76 100644 --- a/core/lib/src/request/form/error.rs +++ b/core/lib/src/request/form/error.rs @@ -1,3 +1,4 @@ +use std::io; use http::RawStr; /// Error returned by the [`FromForm`] derive on form parsing errors. @@ -8,7 +9,7 @@ use http::RawStr; /// * `BadValue` or `Unknown` in incoming form string field order /// * `Missing` in lexical field order #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum FormError<'f> { +pub enum FormParseError<'f> { /// The field named `.0` with value `.1` failed to parse or validate. BadValue(&'f RawStr, &'f RawStr), /// The parse was strict and the field named `.0` with value `.1` appeared @@ -19,3 +20,14 @@ pub enum FormError<'f> { /// The field named `.0` was expected but is missing in the incoming form. Missing(&'f RawStr), } + +/// Error returned by the [`FromData`] implementations of [`Form`] and +/// [`LenientForm`]. +#[derive(Debug)] +pub enum FormDataError<'f, E> { + Io(io::Error), + Malformed(&'f str), + Parse(E, &'f str) +} + +pub type FormError<'f> = FormDataError<'f, FormParseError<'f>>; diff --git a/core/lib/src/request/form/form.rs b/core/lib/src/request/form/form.rs index abde9221..7772c05d 100644 --- a/core/lib/src/request/form/form.rs +++ b/core/lib/src/request/form/form.rs @@ -1,13 +1,13 @@ -use std::marker::PhantomData; -use std::fmt::{self, Debug}; +use std::ops::Deref; -use request::Request; -use data::{self, Data, FromData}; -use request::form::{FromForm, FormItems}; +use outcome::Outcome::*; +use request::{Request, form::{FromForm, FormItems, FormDataError}}; +use data::{Outcome, Transform, Transformed, Data, FromData}; +use http::Status; -/// A `FromData` type for parsing `FromForm` types strictly. +/// A data guard for parsing [`FromForm`] types strictly. /// -/// This type implements the `FromData` trait. It provides a generic means to +/// This type implements the [`FromData]` trait. It provides a generic means to /// parse arbitrary structures from incoming form data. /// /// # Strictness @@ -17,15 +17,13 @@ use request::form::{FromForm, FormItems}; /// error on missing and/or extra fields. For instance, if an incoming form /// contains the fields "a", "b", and "c" while `T` only contains "a" and "c", /// the form _will not_ parse as `Form`. If you would like to admit extra -/// fields without error, see -/// [`LenientForm`](/rocket/request/struct.LenientForm.html). +/// fields without error, see [`LenientForm`]. /// /// # Usage /// /// This type can be used with any type that implements the `FromForm` trait. -/// The trait can be automatically derived; see the -/// [FromForm](trait.FromForm.html) documentation for more information on -/// deriving or implementing the trait. +/// The trait can be automatically derived; see the [`FromForm`] documentation +/// for more information on deriving or implementing the trait. /// /// Because `Form` implements `FromData`, it can be used directly as a target of /// the `data = ""` route parameter. For instance, if some structure of @@ -38,57 +36,32 @@ use request::form::{FromForm, FormItems}; /// fn submit(form: Form) ... { ... } /// ``` /// -/// To preserve memory safety, if the underlying structure type contains -/// references into form data, the type can only be borrowed via the -/// [get](#method.get) or [get_mut](#method.get_mut) methods. Otherwise, the -/// parsed structure can be retrieved with the [into_inner](#method.into_inner) -/// method. -/// -/// ## With References -/// -/// The simplest data structure with a reference into form data looks like this: -/// -/// ```rust -/// # #![feature(plugin, decl_macro)] -/// # #![allow(deprecated, dead_code, unused_attributes)] -/// # #![plugin(rocket_codegen)] -/// # #[macro_use] extern crate rocket; -/// # use rocket::http::RawStr; -/// #[derive(FromForm)] -/// struct UserInput<'f> { -/// value: &'f RawStr -/// } -/// # fn main() { } -/// ``` -/// -/// This corresponds to a form with a single field named `value` that should be -/// a string. A handler for this type can be written as: +/// A type of `Form` automatically dereferences into an `&T`, though you can +/// also tranform a `Form` into a `T` by calling +/// [`into_inner()`](Form::into_inner()). Thanks automatic dereferencing, you +/// can access fields of `T` transparently through a `Form`: /// /// ```rust /// # #![feature(plugin, decl_macro)] /// # #![allow(deprecated, unused_attributes)] /// # #![plugin(rocket_codegen)] /// # #[macro_use] extern crate rocket; -/// # use rocket::request::Form; -/// # use rocket::http::RawStr; -/// # #[derive(FromForm)] -/// # struct UserInput<'f> { -/// # value: &'f RawStr -/// # } +/// use rocket::request::Form; +/// use rocket::http::RawStr; +/// +/// #[derive(FromForm)] +/// struct UserInput<'f> { +/// value: &'f RawStr +/// } +/// /// #[post("/submit", data = "")] -/// fn submit_task<'r>(user_input: Form<'r, UserInput<'r>>) -> String { -/// format!("Your value: {}", user_input.get().value) +/// fn submit_task(user_input: Form) -> String { +/// format!("Your value: {}", user_input.value) /// } /// # fn main() { } /// ``` /// -/// Note that the `` `r`` lifetime is used _twice_ in the handler's signature: -/// this is necessary to tie the lifetime of the structure to the lifetime of -/// the request data. -/// -/// ## Without References -/// -/// The owned analog of the `UserInput` type above is: +/// For posterity, the owned analog of the `UserInput` type above is: /// /// ```rust /// struct OwnedUserInput { @@ -96,7 +69,7 @@ use request::form::{FromForm, FormItems}; /// } /// ``` /// -/// The handler is written similarly: +/// A handler that handles a form of this type can similarly by written: /// /// ```rust /// # #![feature(plugin, decl_macro)] @@ -110,8 +83,7 @@ use request::form::{FromForm, FormItems}; /// # } /// #[post("/submit", data = "")] /// fn submit_task(user_input: Form) -> String { -/// let input: OwnedUserInput = user_input.into_inner(); -/// format!("Your value: {}", input.value) +/// format!("Your value: {}", user_input.value) /// } /// # fn main() { } /// ``` @@ -126,7 +98,7 @@ use request::form::{FromForm, FormItems}; /// depends on your use case. The primary question to answer is: _Can the input /// contain characters that must be URL encoded?_ Note that this includes /// common characters such as spaces. If so, then you must use `String`, whose -/// `FromFormValue` implementation automatically URL decodes strings. Because +/// [`FromFormValue`] implementation automatically URL decodes strings. Because /// the `&RawStr` references will refer directly to the underlying form data, /// they will be raw and URL encoded. /// @@ -146,119 +118,11 @@ use request::form::{FromForm, FormItems}; /// [global.limits] /// forms = 524288 /// ``` -pub struct Form<'f, T: FromForm<'f> + 'f> { - object: T, - form_string: String, - _phantom: PhantomData<&'f T>, -} +#[derive(Debug)] +pub struct Form(T); -pub enum FormResult { - Ok(T), - Err(String, E), - Invalid(String) -} - -impl<'f, T: FromForm<'f> + 'f> Form<'f, T> { - /// Immutably borrow the parsed type. - /// - /// # Example - /// - /// ```rust - /// # #![feature(plugin, decl_macro)] - /// # #![plugin(rocket_codegen)] - /// # #[macro_use] extern crate rocket; - /// use rocket::request::Form; - /// - /// #[derive(FromForm)] - /// struct MyForm { - /// field: String, - /// } - /// - /// #[post("/submit", data = "
")] - /// fn submit(form: Form) -> String { - /// format!("Form field is: {}", form.get().field) - /// } - /// # - /// # fn main() { } - /// ``` - #[inline(always)] - pub fn get(&'f self) -> &T { - &self.object - } - - /// Returns the raw form string that was used to parse the encapsulated - /// object. - /// - /// # Example - /// - /// ```rust - /// # #![feature(plugin, decl_macro)] - /// # #![plugin(rocket_codegen)] - /// # #[macro_use] extern crate rocket; - /// use rocket::request::Form; - /// - /// #[derive(FromForm)] - /// struct MyForm { - /// field: String, - /// } - /// - /// #[post("/submit", data = "")] - /// fn submit(form: Form) -> String { - /// format!("Raw form string is: {}", form.raw_form_string()) - /// } - /// # - /// # fn main() { } - #[inline(always)] - pub fn raw_form_string(&'f self) -> &str { - &self.form_string - } - - // Alright, so here's what's going on here. We'd like to have form - // objects have pointers directly to the form string. This means that - // the form string has to live at least as long as the form object. So, - // to enforce this, we store the form_string along with the form object. - // - // So far so good. Now, this means that the form_string can never be - // deallocated while the object is alive. That implies that the - // `form_string` value should never be moved away. We can enforce that - // easily by 1) not making `form_string` public, and 2) not exposing any - // `&mut self` methods that could modify `form_string`. - // - // Okay, we do all of these things. Now, we still need to give a - // lifetime to `FromForm`. Which one do we choose? The danger is that - // references inside `object` may be copied out, and we have to ensure - // that they don't outlive this structure. So we would really like - // something like `self` and then to transmute to that. But this doesn't - // exist. So we do the next best: we use the first lifetime supplied by the - // caller via `get()` and constrain everything to that lifetime. This is, in - // reality a little coarser than necessary, but the user can simply move the - // call to right after the creation of a Form object to get the same effect. - crate fn new(string: String, strict: bool) -> FormResult { - let long_lived_string: &'f str = unsafe { - ::std::mem::transmute(string.as_str()) - }; - - let mut items = FormItems::from(long_lived_string); - let result = T::from_form(items.by_ref(), strict); - if !items.exhaust() { - return FormResult::Invalid(string); - } - - match result { - Ok(obj) => FormResult::Ok(Form { - form_string: string, - object: obj, - _phantom: PhantomData - }), - Err(e) => FormResult::Err(string, e) - } - } -} - -impl<'f, T: FromForm<'f> + 'static> Form<'f, T> { - /// Consumes `self` and returns the parsed value. For safety reasons, this - /// method may only be called when the parsed value contains no - /// non-`'static` references. +impl Form { + /// Consumes `self` and returns the parsed value. /// /// # Example /// @@ -277,39 +141,89 @@ impl<'f, T: FromForm<'f> + 'static> Form<'f, T> { /// fn submit(form: Form) -> String { /// form.into_inner().field /// } - /// # /// # fn main() { } #[inline(always)] pub fn into_inner(self) -> T { - self.object + self.0 } } -impl<'f, T: FromForm<'f> + Debug + 'f> Debug for Form<'f, T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?} from form string: {:?}", self.object, self.form_string) +impl Deref for Form { + type Target = T; + + fn deref(&self) -> &T { + &self.0 } } -impl<'f, T: FromForm<'f>> FromData for Form<'f, T> where T::Error: Debug { - /// The raw form string, if it was able to be retrieved from the request. - type Error = Option; +impl<'f, T: FromForm<'f>> Form { + crate fn from_data( + form_str: &'f str, + strict: bool + ) -> Outcome> { + use self::FormDataError::*; - /// Parses a `Form` from incoming form data. - /// - /// If the content type of the request data is not - /// `application/x-www-form-urlencoded`, `Forward`s the request. If the form - /// data cannot be parsed into a `T`, a `Failure` with status code - /// `UnprocessableEntity` is returned. If the form string is malformed, a - /// `Failure` with status code `BadRequest` is returned. Finally, if reading - /// the incoming stream fails, returns a `Failure` with status code - /// `InternalServerError`. In all failure cases, the raw form string is - /// returned if it was able to be retrieved from the incoming stream. - /// - /// All relevant warnings and errors are written to the console in Rocket - /// logging format. - #[inline] - fn from_data(request: &Request, data: Data) -> data::Outcome { - super::from_data(request, data, true) + let mut items = FormItems::from(form_str); + let result = T::from_form(&mut items, strict); + if !items.exhaust() { + error_!("The request's form string was malformed."); + return Failure((Status::BadRequest, Malformed(form_str))); + } + + match result { + Ok(v) => Success(v), + Err(e) => { + error_!("The incoming form failed to parse."); + Failure((Status::UnprocessableEntity, Parse(e, form_str))) + } + } + } +} + +/// Parses a `Form` from incoming form data. +/// +/// If the content type of the request data is not +/// `application/x-www-form-urlencoded`, `Forward`s the request. If the form +/// data cannot be parsed into a `T`, a `Failure` with status code +/// `UnprocessableEntity` is returned. If the form string is malformed, a +/// `Failure` with status code `BadRequest` is returned. Finally, if reading the +/// incoming stream fails, returns a `Failure` with status code +/// `InternalServerError`. In all failure cases, the raw form string is returned +/// if it was able to be retrieved from the incoming stream. +/// +/// All relevant warnings and errors are written to the console in Rocket +/// logging format. +impl<'f, T: FromForm<'f>> FromData<'f> for Form { + type Error = FormDataError<'f, T::Error>; + type Owned = String; + type Borrowed = str; + + fn transform( + request: &Request, + data: Data + ) -> Transform> { + use std::{cmp::min, io::Read}; + + let outcome = 'o: { + if !request.content_type().map_or(false, |ct| ct.is_form()) { + warn_!("Form data does not have form content type."); + break 'o Forward(data); + } + + let limit = request.limits().forms; + let mut stream = data.open().take(limit); + let mut form_string = String::with_capacity(min(4096, limit) as usize); + if let Err(e) = stream.read_to_string(&mut form_string) { + break 'o Failure((Status::InternalServerError, FormDataError::Io(e))); + } + + break 'o Success(form_string); + }; + + Transform::Borrowed(outcome) + } + + fn from_data(_: &Request, o: Transformed<'f, Self>) -> Outcome { + >::from_data(o.borrowed()?, true).map(Form) } } diff --git a/core/lib/src/request/form/from_form.rs b/core/lib/src/request/form/from_form.rs index 93af9c22..dcf243a8 100644 --- a/core/lib/src/request/form/from_form.rs +++ b/core/lib/src/request/form/from_form.rs @@ -43,7 +43,7 @@ use request::FormItems; /// # struct TodoTask { description: String, completed: bool } /// #[post("/submit", data = "")] /// fn submit_task(task: Form) -> String { -/// format!("New task: {}", task.get().description) +/// format!("New task: {}", task.description) /// } /// # fn main() { } /// ``` diff --git a/core/lib/src/request/form/lenient.rs b/core/lib/src/request/form/lenient.rs index 1e395718..4b0d0712 100644 --- a/core/lib/src/request/form/lenient.rs +++ b/core/lib/src/request/form/lenient.rs @@ -1,16 +1,14 @@ -use std::fmt::{self, Debug}; +use std::ops::Deref; -use request::Request; -use request::form::{Form, FromForm}; -use data::{self, Data, FromData}; +use request::{Request, form::{Form, FormDataError, FromForm}}; +use data::{Data, Transform, Transformed, FromData, Outcome}; -/// A `FromData` type for parsing `FromForm` types leniently. +/// A data gaurd for parsing [`FromForm`] types leniently. /// -/// This type implements the `FromData` trait, and like -/// [`Form`](/rocket/request/struct.Form.html), provides a generic means to -/// parse arbitrary structures from incoming form data. Unlike `Form`, this type -/// uses a _lenient_ parsing strategy: forms that contains a superset of the -/// expected fields (i.e, extra fields) will parse successfully. +/// This type implements the [`FromData`] trait, and like [`Form`], provides a +/// generic means to parse arbitrary structures from incoming form data. Unlike +/// `Form`, this type uses a _lenient_ parsing strategy: forms that contains a +/// superset of the expected fields (i.e, extra fields) will parse successfully. /// /// # Leniency /// @@ -22,9 +20,8 @@ use data::{self, Data, FromData}; /// /// # Usage /// -/// The usage of a `LenientForm` type is equivalent to that of -/// [`Form`](/rocket/request/struct.Form.html), so we defer details to its -/// documentation. We provide shallow information here. +/// The usage of a `LenientForm` type is equivalent to that of [`Form`], so we +/// defer details to its documentation. /// /// `LenientForm` implements `FromData`, so it can be used directly as a target /// of the `data = ""` route parameter. For instance, if some structure @@ -32,9 +29,23 @@ use data::{self, Data, FromData}; /// automatically parsed into the `T` structure with the following route and /// handler: /// -/// ```rust,ignore -/// #[post("/form_submit", data = "")] -/// fn submit(form: LenientForm) ... { ... } +/// ```rust +/// # #![feature(plugin, decl_macro)] +/// # #![allow(deprecated, unused_attributes)] +/// # #![plugin(rocket_codegen)] +/// # #[macro_use] extern crate rocket; +/// use rocket::request::LenientForm; +/// +/// #[derive(FromForm)] +/// struct UserInput { +/// value: String +/// } +/// +/// #[post("/submit", data = "")] +/// fn submit_task(user_input: LenientForm) -> String { +/// format!("Your value: {}", user_input.value) +/// } +/// # fn main() { } /// ``` /// /// ## Incoming Data Limits @@ -48,68 +59,11 @@ use data::{self, Data, FromData}; /// [global.limits] /// forms = 524288 /// ``` -pub struct LenientForm<'f, T: FromForm<'f> + 'f>(Form<'f, T>); +#[derive(Debug)] +pub struct LenientForm(T); -impl<'f, T: FromForm<'f> + 'f> LenientForm<'f, T> { - /// Immutably borrow the parsed type. - /// - /// # Example - /// - /// ```rust - /// # #![feature(plugin, decl_macro)] - /// # #![plugin(rocket_codegen)] - /// # #[macro_use] extern crate rocket; - /// use rocket::request::LenientForm; - /// - /// #[derive(FromForm)] - /// struct MyForm { - /// field: String, - /// } - /// - /// #[post("/submit", data = "")] - /// fn submit(form: LenientForm) -> String { - /// format!("Form field is: {}", form.get().field) - /// } - /// # - /// # fn main() { } - /// ``` - #[inline(always)] - pub fn get(&'f self) -> &T { - self.0.get() - } - - /// Returns the raw form string that was used to parse the encapsulated - /// object. - /// - /// # Example - /// - /// ```rust - /// # #![feature(plugin, decl_macro)] - /// # #![plugin(rocket_codegen)] - /// # #[macro_use] extern crate rocket; - /// use rocket::request::LenientForm; - /// - /// #[derive(FromForm)] - /// struct MyForm { - /// field: String, - /// } - /// - /// #[post("/submit", data = "")] - /// fn submit(form: LenientForm) -> String { - /// format!("Raw form string is: {}", form.raw_form_string()) - /// } - /// # - /// # fn main() { } - #[inline(always)] - pub fn raw_form_string(&'f self) -> &str { - self.0.raw_form_string() - } -} - -impl<'f, T: FromForm<'f> + 'static> LenientForm<'f, T> { - /// Consumes `self` and returns the parsed value. For safety reasons, this - /// method may only be called when the parsed value contains no - /// non-`'static` references. +impl LenientForm { + /// Consumes `self` and returns the parsed value. /// /// # Example /// @@ -128,39 +82,31 @@ impl<'f, T: FromForm<'f> + 'static> LenientForm<'f, T> { /// fn submit(form: LenientForm) -> String { /// form.into_inner().field /// } - /// # /// # fn main() { } #[inline(always)] pub fn into_inner(self) -> T { - self.0.into_inner() + self.0 } } -impl<'f, T: FromForm<'f> + Debug + 'f> Debug for LenientForm<'f, T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) +impl Deref for LenientForm { + type Target = T; + + fn deref(&self) -> &T { + &self.0 } } -impl<'f, T: FromForm<'f>> FromData for LenientForm<'f, T> where T::Error: Debug { - /// The raw form string, if it was able to be retrieved from the request. - type Error = Option; +impl<'f, T: FromForm<'f>> FromData<'f> for LenientForm { + type Error = FormDataError<'f, T::Error>; + type Owned = String; + type Borrowed = str; - /// Parses a `LenientForm` from incoming form data. - /// - /// If the content type of the request data is not - /// `application/x-www-form-urlencoded`, `Forward`s the request. If the form - /// data cannot be parsed into a `T`, a `Failure` with status code - /// `UnprocessableEntity` is returned. If the form string is malformed, a - /// `Failure` with status code `BadRequest` is returned. Finally, if reading - /// the incoming stream fails, returns a `Failure` with status code - /// `InternalServerError`. In all failure cases, the raw form string is - /// returned if it was able to be retrieved from the incoming stream. - /// - /// All relevant warnings and errors are written to the console in Rocket - /// logging format. - #[inline] - fn from_data(request: &Request, data: Data) -> data::Outcome { - super::from_data(request, data, false).map(LenientForm) + fn transform(r: &Request, d: Data) -> Transform> { + >::transform(r, d) + } + + fn from_data(_: &Request, o: Transformed<'f, Self>) -> Outcome { + >::from_data(o.borrowed()?, false).map(LenientForm) } } diff --git a/core/lib/src/request/form/mod.rs b/core/lib/src/request/form/mod.rs index f4749ed4..cd6854c6 100644 --- a/core/lib/src/request/form/mod.rs +++ b/core/lib/src/request/form/mod.rs @@ -1,20 +1,4 @@ -//! Types and traits to handle form processing. -//! -//! In general, you will deal with forms in Rocket via the `form` parameter in -//! routes: -//! -//! ```rust,ignore -//! #[post("/", form = )] -//! fn form_submit(my_form: MyType) -> ... -//! ``` -//! -//! Form parameter types must implement the [FromForm](trait.FromForm.html) -//! trait, which is auto-derivable. Automatically deriving `FromForm` for a -//! structure requires that all of its fields implement -//! [FromFormValue](trait.FormFormValue.html), which parses and validates form -//! fields. See the [codegen](/rocket_codegen/) documentation or the [forms -//! guide](/guide/forms) for more information on forms and on deriving -//! `FromForm`. +//! Types and traits for form processing. mod form_items; mod from_form; @@ -28,137 +12,4 @@ pub use self::from_form::FromForm; pub use self::from_form_value::FromFormValue; pub use self::form::Form; pub use self::lenient::LenientForm; -pub use self::error::FormError; - -use std::cmp; -use std::io::Read; -use std::fmt::Debug; - -use outcome::Outcome::*; -use request::Request; -use data::{self, Data}; -use self::form::FormResult; -use http::Status; - -fn from_data<'f, T>(request: &Request, - data: Data, - strict: bool - ) -> data::Outcome, Option> - where T: FromForm<'f>, T::Error: Debug -{ - if !request.content_type().map_or(false, |ct| ct.is_form()) { - warn_!("Form data does not have form content type."); - return Forward(data); - } - - let limit = request.limits().forms; - let mut form_string = String::with_capacity(cmp::min(4096, limit) as usize); - let mut stream = data.open().take(limit); - if let Err(e) = stream.read_to_string(&mut form_string) { - error_!("IO Error: {:?}", e); - Failure((Status::InternalServerError, None)) - } else { - match Form::new(form_string, strict) { - FormResult::Ok(form) => Success(form), - FormResult::Invalid(form_string) => { - error_!("The request's form string was malformed."); - Failure((Status::BadRequest, Some(form_string))) - } - FormResult::Err(form_string, e) => { - error_!("Failed to parse value from form: {:?}", e); - Failure((Status::UnprocessableEntity, Some(form_string))) - } - } - } -} - -#[cfg(test)] -mod test { - use super::{Form, FormResult}; - use ::request::{FromForm, FormItems}; - - impl FormResult { - fn unwrap(self) -> T { - match self { - FormResult::Ok(val) => val, - _ => panic!("Unwrapping non-Ok FormResult.") - } - } - } - - struct Simple<'s> { - value: &'s str - } - - struct Other { - value: String - } - - impl<'s> FromForm<'s> for Simple<'s> { - type Error = (); - - fn from_form(items: &mut FormItems<'s>, _: bool) -> Result, ()> { - Ok(Simple { value: items.inner_str() }) - } - } - - impl<'s> FromForm<'s> for Other { - type Error = (); - - fn from_form(items: &mut FormItems<'s>, _: bool) -> Result { - Ok(Other { value: items.inner_str().to_string() }) - } - } - - #[test] - fn test_lifetime() { - let form_string = "hello=world".to_string(); - let form: Form = Form::new(form_string, true).unwrap(); - - let string: &str = form.get().value; - assert_eq!(string, "hello=world"); - } - - #[test] - fn test_lifetime_2() { - let form_string = "hello=world".to_string(); - let mut _y = "hi"; - let _form: Form = Form::new(form_string, true).unwrap(); - // _y = form.get().value; - - // fn should_not_compile<'f>(form: Form<'f, &'f str>) -> &'f str { - // form.get() - // } - - // fn should_not_compile_2<'f>(form: Form<'f, Simple<'f>>) -> &'f str { - // form.into_inner().value - // } - - // assert_eq!(should_not_compile(form), "hello=world"); - } - - #[test] - fn test_lifetime_3() { - let form_string = "hello=world".to_string(); - let form: Form = Form::new(form_string, true).unwrap(); - - // Not bad. - fn should_compile(form: Form) -> String { - form.into_inner().value - } - - assert_eq!(should_compile(form), "hello=world".to_string()); - } - - #[test] - fn test_lifetime_4() { - let form_string = "hello=world".to_string(); - let form: Form = Form::new(form_string, true).unwrap(); - - fn should_compile<'f>(_form: Form<'f, Simple<'f>>) { } - - should_compile(form) - // assert_eq!(should_not_compile(form), "hello=world"); - } -} - +pub use self::error::{FormError, FormParseError, FormDataError}; diff --git a/core/lib/src/request/mod.rs b/core/lib/src/request/mod.rs index a4509ed8..90151e20 100644 --- a/core/lib/src/request/mod.rs +++ b/core/lib/src/request/mod.rs @@ -12,7 +12,8 @@ mod tests; pub use self::request::Request; pub use self::from_request::{FromRequest, Outcome}; pub use self::param::{FromParam, FromSegments}; -pub use self::form::{Form, FormError, LenientForm, FromForm, FromFormValue, FormItems}; +pub use self::form::{Form, LenientForm, FormItems}; +pub use self::form::{FromForm, FormError, FromFormValue, FormParseError, FormDataError}; pub use self::state::State; #[doc(inline)] diff --git a/core/lib/tests/form_method-issue-45.rs b/core/lib/tests/form_method-issue-45.rs index 4cde921c..09e05bb7 100644 --- a/core/lib/tests/form_method-issue-45.rs +++ b/core/lib/tests/form_method-issue-45.rs @@ -12,7 +12,7 @@ struct FormData { #[patch("/", data = "")] fn bug(form_data: Form) -> &'static str { - assert_eq!("Form data", &form_data.get().form_data); + assert_eq!("Form data", form_data.form_data); "OK" } diff --git a/core/lib/tests/local-request-content-type-issue-505.rs b/core/lib/tests/local-request-content-type-issue-505.rs index 344a50db..2ff000ed 100644 --- a/core/lib/tests/local-request-content-type-issue-505.rs +++ b/core/lib/tests/local-request-content-type-issue-505.rs @@ -21,9 +21,9 @@ impl<'a, 'r> FromRequest<'a, 'r> for HasContentType { } } -use rocket::data::{self, FromData}; +use rocket::data::{self, FromDataSimple}; -impl FromData for HasContentType { +impl FromDataSimple for HasContentType { type Error = (); fn from_data(request: &Request, data: Data) -> data::Outcome { diff --git a/core/lib/tests/strict_and_lenient_forms.rs b/core/lib/tests/strict_and_lenient_forms.rs index 5a39edf0..5ff24a77 100644 --- a/core/lib/tests/strict_and_lenient_forms.rs +++ b/core/lib/tests/strict_and_lenient_forms.rs @@ -12,13 +12,13 @@ struct MyForm<'r> { } #[post("/strict", data = "")] -fn strict<'r>(form: Form<'r, MyForm<'r>>) -> String { - form.get().field.as_str().into() +fn strict<'r>(form: Form>) -> String { + form.field.as_str().into() } #[post("/lenient", data = "")] -fn lenient<'r>(form: LenientForm<'r, MyForm<'r>>) -> String { - form.get().field.as_str().into() +fn lenient<'r>(form: LenientForm>) -> String { + form.field.as_str().into() } mod strict_and_lenient_forms_tests { diff --git a/examples/form_kitchen_sink/src/main.rs b/examples/form_kitchen_sink/src/main.rs index cd9574d1..296bd56a 100644 --- a/examples/form_kitchen_sink/src/main.rs +++ b/examples/form_kitchen_sink/src/main.rs @@ -5,7 +5,7 @@ use std::io; -use rocket::request::Form; +use rocket::request::{Form, FormError, FormDataError}; use rocket::response::NamedFile; use rocket::http::RawStr; @@ -29,11 +29,13 @@ struct FormInput<'r> { } #[post("/", data = "")] -fn sink<'r>(sink: Result>, Option>) -> String { +fn sink(sink: Result, FormError>) -> String { match sink { - Ok(form) => format!("{:?}", form.get()), - Err(Some(f)) => format!("Invalid form input: {}", f), - Err(None) => format!("Form input was invalid UTF8."), + Ok(form) => format!("{:?}", &*form), + Err(FormDataError::Io(_)) => format!("Form input was invalid UTF-8."), + Err(FormDataError::Malformed(f)) | Err(FormDataError::Parse(_, f)) => { + format!("Invalid form input: {}", f) + } } } diff --git a/examples/form_kitchen_sink/src/tests.rs b/examples/form_kitchen_sink/src/tests.rs index 9be834de..6c5d4f4a 100644 --- a/examples/form_kitchen_sink/src/tests.rs +++ b/examples/form_kitchen_sink/src/tests.rs @@ -177,6 +177,6 @@ fn check_bad_utf8() { let client = Client::new(rocket()).unwrap(); unsafe { let bad_str = ::std::str::from_utf8_unchecked(b"a=\xff"); - assert_form_eq(&client, bad_str, "Form input was invalid UTF8.".into()); + assert_form_eq(&client, bad_str, "Form input was invalid UTF-8.".into()); } } diff --git a/examples/form_validation/src/main.rs b/examples/form_validation/src/main.rs index 5e70137a..e3bd26dd 100644 --- a/examples/form_validation/src/main.rs +++ b/examples/form_validation/src/main.rs @@ -51,10 +51,8 @@ impl<'v> FromFormValue<'v> for AdultAge { } } -#[post("/login", data = "")] -fn login<'a>(user_form: Form<'a, UserLogin<'a>>) -> Result { - let user = user_form.get(); - +#[post("/login", data = "")] +fn login(user: Form) -> Result { if let Err(e) = user.age { return Err(format!("Age is invalid: {}", e)); } diff --git a/examples/session/src/main.rs b/examples/session/src/main.rs index 384a9402..3ae3a8aa 100644 --- a/examples/session/src/main.rs +++ b/examples/session/src/main.rs @@ -37,7 +37,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for User { #[post("/login", data = "")] fn login(mut cookies: Cookies, login: Form) -> Result> { - if login.get().username == "Sergio" && login.get().password == "password" { + if login.username == "Sergio" && login.password == "password" { cookies.add_private(Cookie::new("user_id", 1.to_string())); Ok(Redirect::to(uri!(index))) } else {