diff --git a/codegen/src/decorators/derive_form.rs b/codegen/src/decorators/derive_form.rs index 9145eedd..ae02de85 100644 --- a/codegen/src/decorators/derive_form.rs +++ b/codegen/src/decorators/derive_form.rs @@ -79,7 +79,7 @@ pub fn from_form_derive(ecx: &mut ExtCtxt, span: Span, meta_item: &MetaItem, generics: trait_generics, methods: vec![ MethodDef { - name: "from_form_items", + name: "from_form", generics: ty::LifetimeBounds::empty(), explicit_self: None, args: vec![ @@ -91,19 +91,23 @@ pub fn from_form_derive(ecx: &mut ExtCtxt, span: Span, meta_item: &MetaItem, global: true })), ty::Borrowed(None, Mutability::Mutable) - ) - ], - ret_ty: ty::Ty::Literal( - ty::Path { - path: vec!["std", "result", "Result"], + ), + ty::Literal(ty::Path { + path: vec!["bool"], lifetime: None, - params: vec![ - Box::new(ty::Ty::Self_), - Box::new(error_type.clone()) - ], - global: true, - } - ), + params: vec![], + global: false, + }) + ], + ret_ty: ty::Literal(ty::Path { + path: vec!["std", "result", "Result"], + lifetime: None, + params: vec![ + Box::new(ty::Ty::Self_), + Box::new(error_type.clone()) + ], + global: true, + }), attributes: vec![], is_unsafe: false, combine_substructure: c_s(Box::new(from_form_substructure)), @@ -173,16 +177,16 @@ pub fn extract_field_ident_name(ecx: &ExtCtxt, struct_field: &StructField) fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substructure) -> P { // Check that we specified the methods to the argument correctly. - const EXPECTED_ARGS: usize = 1; - let arg = if substr.nonself_args.len() == EXPECTED_ARGS { - &substr.nonself_args[0] + const EXPECTED_ARGS: usize = 2; + let (items_arg, strict_arg) = if substr.nonself_args.len() == EXPECTED_ARGS { + (&substr.nonself_args[0], &substr.nonself_args[1]) } else { let msg = format!("incorrect number of arguments in `from_form_string`: \ expected {}, found {}", EXPECTED_ARGS, substr.nonself_args.len()); cx.span_bug(trait_span, msg.as_str()); }; - debug!("argument is: {:?}", arg); + debug!("arguments are: {:?}, {:?}", items_arg, strict_arg); // Ensure the the fields are from a 'StaticStruct' and extract them. let fields = match *substr.fields { @@ -251,18 +255,17 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct // The actual match statement. Iterate through all of the fields in the form // and use the $arms generated above. stmts.push(quote_stmt!(cx, - for (__k, __v) in $arg { + for (__k, __v) in $items_arg { match __k.as_str() { $arms - "_method" => { - /* This is a Rocket-specific field. If the user hasn't asked - * for it, just let it go by without error. This should stay - * in sync with Rocket::preprocess. */ - } _ => { - println!(" => {}={} has no matching field in struct.", - __k, __v); - $return_err_stmt + // If we're parsing strictly, emit an error for everything + // the user hasn't asked for. Keep synced with 'preprocess'. + if $strict_arg && __k != "_method" { + println!(" => {}={} has no matching field in struct.", + __k, __v); + $return_err_stmt + } } }; } diff --git a/codegen/src/decorators/route.rs b/codegen/src/decorators/route.rs index f8797db9..b4f1ed9e 100644 --- a/codegen/src/decorators/route.rs +++ b/codegen/src/decorators/route.rs @@ -76,7 +76,8 @@ impl RouteGenerateExt for RouteParams { Some(quote_stmt!(ecx, let $name: $ty = { let mut items = ::rocket::request::FormItems::from($form_string); - let obj = match ::rocket::request::FromForm::from_form_items(items.by_ref()) { + let form = ::rocket::request::FromForm::from_form(items.by_ref(), true); + let obj = match form { Ok(v) => v, Err(_) => return ::rocket::Outcome::Forward(__data) }; diff --git a/codegen/tests/run-pass/derive_form.rs b/codegen/tests/run-pass/derive_form.rs index 522f0935..ded29fbf 100644 --- a/codegen/tests/run-pass/derive_form.rs +++ b/codegen/tests/run-pass/derive_form.rs @@ -70,9 +70,9 @@ struct FieldNamedV<'r> { v: &'r RawStr, } -fn parse<'f, T: FromForm<'f>>(string: &'f str) -> Option { +fn parse<'f, T: FromForm<'f>>(string: &'f str, strict: bool) -> Option { let mut items = FormItems::from(string); - let result = T::from_form_items(items.by_ref()); + let result = T::from_form(items.by_ref(), strict); if !items.exhaust() { panic!("Invalid form input."); } @@ -80,20 +80,28 @@ fn parse<'f, T: FromForm<'f>>(string: &'f str) -> Option { result.ok() } +fn strict<'f, T: FromForm<'f>>(string: &'f str) -> Option { + parse(string, true) +} + +fn lenient<'f, T: FromForm<'f>>(string: &'f str) -> Option { + parse(string, false) +} + fn main() { // Same number of arguments: simple case. - let task: Option = parse("description=Hello&completed=on"); + let task: Option = strict("description=Hello&completed=on"); assert_eq!(task, Some(TodoTask { description: "Hello".to_string(), completed: true })); // Argument in string but not in form. - let task: Option = parse("other=a&description=Hello&completed=on"); + let task: Option = strict("other=a&description=Hello&completed=on"); assert!(task.is_none()); // Ensure _method isn't required. - let task: Option = parse("_method=patch&description=Hello&completed=off"); + let task: Option = strict("_method=patch&description=Hello&completed=off"); assert_eq!(task, Some(TodoTask { description: "Hello".to_string(), completed: false @@ -104,7 +112,7 @@ fn main() { "checkbox=off", "textarea=", "select=a", "radio=c", ].join("&"); - let input: Option = parse(&form_string); + let input: Option = strict(&form_string); assert_eq!(input, Some(FormInput { checkbox: false, number: 10, @@ -115,41 +123,77 @@ fn main() { })); // Argument not in string with default in form. - let default: Option = parse(""); + let default: Option = strict(""); assert_eq!(default, Some(DefaultInput { arg: None })); // Ensure _method can be captured if desired. - let manual: Option = parse("_method=put&done=true"); + let manual: Option = strict("_method=put&done=true"); + assert_eq!(manual, Some(ManualMethod { + _method: Some("put".into()), + done: true + })); + + let manual: Option = lenient("_method=put&done=true"); assert_eq!(manual, Some(ManualMethod { _method: Some("put".into()), done: true })); // And ignored when not present. - let manual: Option = parse("done=true"); + let manual: Option = strict("done=true"); assert_eq!(manual, Some(ManualMethod { _method: None, done: true })); // Check that a `bool` value that isn't in the form is marked as `false`. - let manual: Option = parse(""); + let manual: Option = strict(""); assert_eq!(manual, Some(UnpresentCheckbox { checkbox: false })); // Check that a `bool` value that isn't in the form is marked as `false`. - let manual: Option = parse("something=hello"); + let manual: Option = strict("something=hello"); assert_eq!(manual, Some(UnpresentCheckboxTwo { checkbox: false, something: "hello".into() })); // Check that a structure with one field `v` parses correctly. - let manual: Option = parse("v=abc"); + let manual: Option = strict("v=abc"); assert_eq!(manual, Some(FieldNamedV { v: "abc".into() })); + + // Check that a structure with one field `v` parses correctly (lenient). + let manual: Option = lenient("v=abc"); + assert_eq!(manual, Some(FieldNamedV { v: "abc".into() })); + + let manual: Option = lenient("v=abc&a=123"); + assert_eq!(manual, Some(FieldNamedV { v: "abc".into() })); + + let manual: Option = lenient("c=abcddef&v=abc&a=123"); + assert_eq!(manual, Some(FieldNamedV { v: "abc".into() })); + + // Check default values (bool) with lenient parsing. + let manual: Option = lenient("something=hello"); + assert_eq!(manual, Some(UnpresentCheckboxTwo { + checkbox: false, + something: "hello".into() + })); + + let manual: Option = lenient("hi=hi&something=hello"); + assert_eq!(manual, Some(UnpresentCheckboxTwo { + checkbox: false, + something: "hello".into() + })); + + // Check that a missing field doesn't parse, even leniently. + let manual: Option = lenient("a=abc"); + assert!(manual.is_none()); + + let manual: Option = lenient("_method=abc"); + assert!(manual.is_none()); } diff --git a/codegen/tests/run-pass/empty_form.rs b/codegen/tests/run-pass/empty_form.rs index 3594e9eb..56d1cae9 100644 --- a/codegen/tests/run-pass/empty_form.rs +++ b/codegen/tests/run-pass/empty_form.rs @@ -10,6 +10,9 @@ struct Form { } fn main() { // Same number of arguments: simple case. - let task = Form::from_form_items(&mut FormItems::from("")); + let task = Form::from_form(&mut FormItems::from(""), true); + assert_eq!(task, Ok(Form { })); + + let task = Form::from_form(&mut FormItems::from(""), false); assert_eq!(task, Ok(Form { })); } diff --git a/codegen/tests/run-pass/form-field-rename.rs b/codegen/tests/run-pass/form-field-rename.rs index 674b6e24..83dcef66 100644 --- a/codegen/tests/run-pass/form-field-rename.rs +++ b/codegen/tests/run-pass/form-field-rename.rs @@ -19,9 +19,9 @@ struct Form { double: String, } -fn parse<'f, T: FromForm<'f>>(string: &'f str) -> Option { +fn parse<'f, T: FromForm<'f>>(string: &'f str, strict: bool) -> Option { let mut items = FormItems::from(string); - let result = T::from_form_items(items.by_ref()); + let result = T::from_form(items.by_ref(), strict); if !items.exhaust() { panic!("Invalid form input."); } @@ -29,13 +29,17 @@ fn parse<'f, T: FromForm<'f>>(string: &'f str) -> Option { result.ok() } +fn parse_strict<'f, T: FromForm<'f>>(string: &'f str) -> Option { + parse(string, true) +} + fn main() { let form_string = &[ "single=100", "camelCase=helloThere", "TitleCase=HiHi", "type=-2", "DOUBLE=bing_bong" ].join("&"); - let form: Option
= parse(&form_string); + let form: Option = parse_strict(&form_string); assert_eq!(form, Some(Form { single: 100, camel_case: "helloThere".into(), @@ -49,6 +53,6 @@ fn main() { "DOUBLE=bing_bong" ].join("&"); - let form: Option = parse(&form_string); + let form: Option = parse_strict(&form_string); assert!(form.is_none()); } diff --git a/lib/src/request/form/form.rs b/lib/src/request/form/form.rs new file mode 100644 index 00000000..8ad71b00 --- /dev/null +++ b/lib/src/request/form/form.rs @@ -0,0 +1,321 @@ +use std::marker::PhantomData; +use std::fmt::{self, Debug}; + +use request::Request; +use data::{self, Data, FromData}; +use request::form::{FromForm, FormItems}; + +/// A `FromData` type for parsing `FromForm` types strictly. +/// +/// This type implements the `FromData` trait. It provides a generic means to +/// parse arbitrary structures from incoming form data. +/// +/// # Strictness +/// +/// A `Form` will parse successfully from an incoming form only if the form +/// contains the exact set of fields in `T`. Said another way, a `Form` will +/// 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). +/// +/// # 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. +/// +/// Because `Form` implements `FromData`, it can be used directly as a target of +/// the `data = ""` route parameter. For instance, if some structure of +/// type `T` implements the `FromForm` trait, an incoming form can be +/// automatically parsed into the `T` structure with the following route and +/// handler: +/// +/// ```rust,ignore +/// #[post("/form_submit", data = "")] +/// 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, custom_derive)] +/// # #![allow(deprecated, dead_code, unused_attributes)] +/// # #![plugin(rocket_codegen)] +/// # 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: +/// +/// ```rust +/// # #![feature(plugin, custom_derive)] +/// # #![allow(deprecated, unused_attributes)] +/// # #![plugin(rocket_codegen)] +/// # extern crate rocket; +/// # 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 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: +/// +/// ```rust +/// # #![feature(plugin, custom_derive)] +/// # #![allow(deprecated, dead_code, unused_attributes)] +/// # #![plugin(rocket_codegen)] +/// # extern crate rocket; +/// #[derive(FromForm)] +/// struct OwnedUserInput { +/// value: String +/// } +/// # fn main() { } +/// ``` +/// +/// The handler is written similarly: +/// +/// ```rust +/// # #![feature(plugin, custom_derive)] +/// # #![allow(deprecated, unused_attributes)] +/// # #![plugin(rocket_codegen)] +/// # extern crate rocket; +/// # use rocket::request::Form; +/// # #[derive(FromForm)] +/// # struct OwnedUserInput { +/// # value: String +/// # } +/// #[post("/submit", data = "")] +/// fn submit_task(user_input: Form) -> String { +/// let input: OwnedUserInput = user_input.into_inner(); +/// format!("Your value: {}", input.value) +/// } +/// # fn main() { } +/// ``` +/// +/// Note that no lifetime annotations are required: Rust is able to infer the +/// lifetime as `` `static``. Because the lifetime is `` `static``, the +/// `into_inner` method can be used to directly retrieve the parsed value. +/// +/// ## Performance and Correctness Considerations +/// +/// Whether you should use a `&RawStr` or `String` in your `FromForm` type +/// 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 +/// commmon characters such as spaces. If so, then you must use `String`, whose +/// `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. +/// +/// If your string values will not contain URL encoded characters, using +/// `&RawStr` will result in fewer allocation and is thus preferred. +/// +/// ## Incoming Data Limits +/// +/// The default size limit for incoming form data is 32KiB. Setting a limit +/// 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.forms` configuration parameter. For +/// instance, to increase the forms limit to 512KiB for all environments, you +/// may add the following to your `Rocket.toml`: +/// +/// ```toml +/// [global.limits] +/// forms = 524288 +/// ``` +pub struct Form<'f, T: FromForm<'f> + 'f> { + object: T, + form_string: String, + _phantom: PhantomData<&'f 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, custom_derive)] + /// # #![plugin(rocket_codegen)] + /// # 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, custom_derive)] + /// # #![plugin(rocket_codegen)] + /// # 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 contrain 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. + pub(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. + /// + /// # Example + /// + /// ```rust + /// # #![feature(plugin, custom_derive)] + /// # #![plugin(rocket_codegen)] + /// # extern crate rocket; + /// use rocket::request::Form; + /// + /// #[derive(FromForm)] + /// struct MyForm { + /// field: String, + /// } + /// + /// #[post("/submit", data = "")] + /// fn submit(form: Form) -> String { + /// form.into_inner().field + /// } + /// # + /// # fn main() { } + #[inline(always)] + pub fn into_inner(self) -> T { + self.object + } +} + +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<'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; + + /// 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) + } +} diff --git a/lib/src/request/form/from_form.rs b/lib/src/request/form/from_form.rs index 3582ed11..68fbc35e 100644 --- a/lib/src/request/form/from_form.rs +++ b/lib/src/request/form/from_form.rs @@ -52,16 +52,16 @@ pub trait FromForm<'f>: Sized { /// The associated error to be returned when parsing fails. type Error; - /// Parses an instance of `Self` from the form items or returns an `Error` - /// if one cannot be parsed. - fn from_form_items(form_items: &mut FormItems<'f>) -> Result; + /// Parses an instance of `Self` from the iterator of form items `it` or + /// returns an instance of `Self::Error` if one cannot be parsed. + fn from_form(it: &mut FormItems<'f>, strict: bool) -> Result; } /// This implementation should only be used during debugging! impl<'f> FromForm<'f> for &'f str { type Error = (); - fn from_form_items(items: &mut FormItems<'f>) -> Result { + fn from_form(items: &mut FormItems<'f>, _: bool) -> Result { items.mark_complete(); Ok(items.inner_str()) } diff --git a/lib/src/request/form/lenient.rs b/lib/src/request/form/lenient.rs new file mode 100644 index 00000000..e4ed3d11 --- /dev/null +++ b/lib/src/request/form/lenient.rs @@ -0,0 +1,166 @@ +use std::fmt::{self, Debug}; + +use request::Request; +use request::form::{Form, FromForm}; +use data::{self, Data, FromData}; + +/// A `FromData` type 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. +/// +/// # Leniency +/// +/// A `LenientForm` will parse successfully from an incoming form if the form +/// contains a superset of the fields in `T`. Said another way, a +/// `LenientForm` automatically discards extra fields without error. For +/// instance, if an incoming form contains the fields "a", "b", and "c" while +/// `T` only contains "a" and "c", the form _will_ parse as `LenientForm`. +/// +/// # 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. +/// +/// `LenientForm` implements `FromData`, so it can be used directly as a target +/// of the `data = ""` route parameter. For instance, if some structure +/// of type `T` implements the `FromForm` trait, an incoming form can be +/// automatically parsed into the `T` structure with the following route and +/// handler: +/// +/// ```rust,ignore +/// #[post("/form_submit", data = "")] +/// fn submit(form: LenientForm) ... { ... } +/// ``` +/// +/// ## Incoming Data Limits +/// +/// A `LenientForm` obeys the same data limits as a `Form` and defaults to +/// 32KiB. The limit can be increased by setting the `limits.forms` +/// configuration parameter. For instance, to increase the forms limit to 512KiB +/// for all environments, you may add the following to your `Rocket.toml`: +/// +/// ```toml +/// [global.limits] +/// forms = 524288 +/// ``` +pub struct LenientForm<'f, T: FromForm<'f> + 'f>(Form<'f, T>); + +impl<'f, T: FromForm<'f> + 'f> LenientForm<'f, T> { + /// Immutably borrow the parsed type. + /// + /// # Example + /// + /// ```rust + /// # #![feature(plugin, custom_derive)] + /// # #![plugin(rocket_codegen)] + /// # 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, custom_derive)] + /// # #![plugin(rocket_codegen)] + /// # 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. + /// + /// # Example + /// + /// ```rust + /// # #![feature(plugin, custom_derive)] + /// # #![plugin(rocket_codegen)] + /// # extern crate rocket; + /// use rocket::request::LenientForm; + /// + /// #[derive(FromForm)] + /// struct MyForm { + /// field: String, + /// } + /// + /// #[post("/submit", data = "")] + /// fn submit(form: LenientForm) -> String { + /// form.into_inner().field + /// } + /// # + /// # fn main() { } + #[inline(always)] + pub fn into_inner(self) -> T { + self.0.into_inner() + } +} + +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<'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; + + /// 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(|form| LenientForm(form)) + } +} diff --git a/lib/src/request/form/mod.rs b/lib/src/request/form/mod.rs index 994c1c55..d48dc6a0 100644 --- a/lib/src/request/form/mod.rs +++ b/lib/src/request/form/mod.rs @@ -19,296 +19,52 @@ mod form_items; mod from_form; mod from_form_value; +mod form; +mod lenient; pub use self::form_items::FormItems; pub use self::from_form::FromForm; pub use self::from_form_value::FromFormValue; +pub use self::form::Form; +pub use self::lenient::LenientForm; -use std::marker::PhantomData; -use std::fmt::{self, Debug}; +use std::cmp; use std::io::Read; +use std::fmt::Debug; -use http::Status; -use request::Request; -use data::{self, Data, FromData}; use outcome::Outcome::*; +use request::Request; +use data::{self, Data}; +use self::form::FormResult; +use http::Status; -// TODO: This works and is safe, but the lifetime appears twice. -/// A `FromData` type for parsing `FromForm` types. -/// -/// This type implements the `FromData` trait. It provides a generic means to -/// parse arbitrary structures from incoming form data. -/// -/// # 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. -/// -/// Because `Form` implement `FromData`, it can be used directly as a target of -/// the `data = ""` route parameter. For instance, if some structure of -/// type `T` implements the `FromForm` trait, an incoming form can be -/// automatically parsed into the `T` structure with the following route and -/// handler: -/// -/// ```rust,ignore -/// #[post("/form_submit", data = "")] -/// 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, custom_derive)] -/// # #![allow(deprecated, dead_code, unused_attributes)] -/// # #![plugin(rocket_codegen)] -/// # 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: -/// -/// ```rust -/// # #![feature(plugin, custom_derive)] -/// # #![allow(deprecated, unused_attributes)] -/// # #![plugin(rocket_codegen)] -/// # extern crate rocket; -/// # 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 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: -/// -/// ```rust -/// # #![feature(plugin, custom_derive)] -/// # #![allow(deprecated, dead_code, unused_attributes)] -/// # #![plugin(rocket_codegen)] -/// # extern crate rocket; -/// #[derive(FromForm)] -/// struct OwnedUserInput { -/// value: String -/// } -/// # fn main() { } -/// ``` -/// -/// The handler is written similarly: -/// -/// ```rust -/// # #![feature(plugin, custom_derive)] -/// # #![allow(deprecated, unused_attributes)] -/// # #![plugin(rocket_codegen)] -/// # extern crate rocket; -/// # use rocket::request::Form; -/// # #[derive(FromForm)] -/// # struct OwnedUserInput { -/// # value: String -/// # } -/// #[post("/submit", data = "")] -/// fn submit_task(user_input: Form) -> String { -/// let input: OwnedUserInput = user_input.into_inner(); -/// format!("Your value: {}", input.value) -/// } -/// # fn main() { } -/// ``` -/// -/// Note that no lifetime annotations are required: Rust is able to infer the -/// lifetime as `` `static``. Because the lifetime is `` `static``, the -/// `into_inner` method can be used to directly retrieve the parsed value. -/// -/// ## Performance and Correctness Considerations -/// -/// Whether you should use a `&RawStr` or `String` in your `FromForm` type -/// 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 -/// commmon characters such as spaces. If so, then you must use `String`, whose -/// `FromFormValue` implementation deserializes the URL encoded string for you. -/// Because the `str` references will refer directly to the underlying form -/// data, they will be raw and URL encoded. -/// -/// If your string values will not contain URL encoded characters, using -/// `RawStr` will result in fewer allocation and is thus preferred. -/// -/// ## Incoming Data Limits -/// -/// The default size limit for incoming form data is 32KiB. Setting a limit -/// 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.forms` configuration parameter. For -/// instance, to increase the forms limit to 512KiB for all environments, you -/// may add the following to your `Rocket.toml`: -/// -/// ```toml -/// [global.limits] -/// forms = 524288 -/// ``` -pub struct Form<'f, T: FromForm<'f> + 'f> { - object: T, - form_string: String, - _phantom: PhantomData<&'f T>, -} - -enum FormResult { - Ok(T), - Err(String, E), - Invalid(String) -} - -#[cfg(test)] -impl FormResult { - fn unwrap(self) -> T { - match self { - FormResult::Ok(val) => val, - _ => panic!("Unwrapping non-Ok FormResult.") - } - } -} - -impl<'f, T: FromForm<'f> + 'f> Form<'f, T> { - /// Immutably borrow the parsed type. - #[inline(always)] - pub fn get(&'f self) -> &'f T { - &self.object +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); } - /// Mutably borrow the parsed type. - #[inline(always)] - pub fn get_mut(&'f mut self) -> &'f mut T { - &mut self.object - } - - /// Returns the raw form string that was used to parse the encapsulated - /// object. - #[inline(always)] - pub fn raw_form_string(&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 contrain 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. - fn new(form_string: String) -> FormResult { - let long_lived_string: &'f str = unsafe { - ::std::mem::transmute(form_string.as_str()) - }; - - let mut items = FormItems::from(long_lived_string); - let result = T::from_form_items(items.by_ref()); - if !items.exhaust() { - return FormResult::Invalid(form_string); - } - - match result { - Ok(obj) => FormResult::Ok(Form { - form_string: form_string, - object: obj, - _phantom: PhantomData - }), - Err(e) => FormResult::Err(form_string, e) - } - } -} - -impl<'f, T: FromForm<'f> + 'static> Form<'f, T> { - /// Consume this object and move out the parsed object. - #[inline(always)] - pub fn into_inner(self) -> T { - self.object - } -} - -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) - } -} - -/// 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 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; - - fn from_data(request: &Request, data: Data) -> data::Outcome { - if !request.content_type().map_or(false, |ct| ct.is_form()) { - warn_!("Form data does not have form content type."); - return Forward(data); - } - - let mut form_string = String::with_capacity(4096); - let limit = request.limits().forms; - 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) { - 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))) - } + 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))) } } } @@ -316,9 +72,18 @@ impl<'f, T: FromForm<'f>> FromData for Form<'f, T> where T::Error: Debug { #[cfg(test)] mod test { - use super::Form; + 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 } @@ -330,7 +95,7 @@ mod test { impl<'s> FromForm<'s> for Simple<'s> { type Error = (); - fn from_form_items(items: &mut FormItems<'s>) -> Result, ()> { + fn from_form(items: &mut FormItems<'s>, _: bool) -> Result, ()> { Ok(Simple { value: items.inner_str() }) } } @@ -338,7 +103,7 @@ mod test { impl<'s> FromForm<'s> for Other { type Error = (); - fn from_form_items(items: &mut FormItems<'s>) -> Result { + fn from_form(items: &mut FormItems<'s>, _: bool) -> Result { Ok(Other { value: items.inner_str().to_string() }) } } @@ -346,7 +111,7 @@ mod test { #[test] fn test_lifetime() { let form_string = "hello=world".to_string(); - let form: Form = Form::new(form_string).unwrap(); + let form: Form = Form::new(form_string, true).unwrap(); let string: &str = form.get().value; assert_eq!(string, "hello=world"); @@ -356,7 +121,7 @@ mod test { fn test_lifetime_2() { let form_string = "hello=world".to_string(); let mut _y = "hi"; - let _form: Form = Form::new(form_string).unwrap(); + 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 { @@ -373,7 +138,7 @@ mod test { #[test] fn test_lifetime_3() { let form_string = "hello=world".to_string(); - let form: Form = Form::new(form_string).unwrap(); + let form: Form = Form::new(form_string, true).unwrap(); // Not bad. fn should_compile(form: Form) -> String { @@ -386,7 +151,7 @@ mod test { #[test] fn test_lifetime_4() { let form_string = "hello=world".to_string(); - let form: Form = Form::new(form_string).unwrap(); + let form: Form = Form::new(form_string, true).unwrap(); fn should_compile<'f>(_form: Form<'f, Simple<'f>>) { } diff --git a/lib/src/request/mod.rs b/lib/src/request/mod.rs index 358fed60..723e07eb 100644 --- a/lib/src/request/mod.rs +++ b/lib/src/request/mod.rs @@ -12,7 +12,7 @@ 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, FromForm, FromFormValue, FormItems}; +pub use self::form::{Form, LenientForm, FromForm, FromFormValue, FormItems}; pub use self::state::State; /// Type alias to retrieve [Flash](/rocket/response/struct.Flash.html) messages diff --git a/lib/tests/strict_and_lenient_forms.rs b/lib/tests/strict_and_lenient_forms.rs new file mode 100644 index 00000000..853320e2 --- /dev/null +++ b/lib/tests/strict_and_lenient_forms.rs @@ -0,0 +1,73 @@ +#![feature(plugin, custom_derive)] +#![plugin(rocket_codegen)] + +extern crate rocket; + +use rocket::request::{Form, LenientForm}; +use rocket::http::RawStr; + +#[derive(FromForm)] +struct MyForm<'r> { + field: &'r RawStr, +} + +#[post("/strict", data = "")] +fn strict<'r>(form: Form<'r, MyForm<'r>>) -> String { + form.get().field.as_str().into() +} + +#[post("/lenient", data = "")] +fn lenient<'r>(form: LenientForm<'r, MyForm<'r>>) -> String { + form.get().field.as_str().into() +} + +mod strict_and_lenient_forms_tests { + use super::*; + use rocket::local::Client; + use rocket::http::{Status, ContentType}; + + const FIELD_VALUE: &str = "just_some_value"; + + fn client() -> Client { + Client::new(rocket::ignite().mount("/", routes![strict, lenient])).unwrap() + } + + #[test] + fn test_strict_form() { + let client = client(); + let mut response = client.post("/strict") + .header(ContentType::Form) + .body(format!("field={}", FIELD_VALUE)) + .dispatch(); + + assert_eq!(response.status(), Status::Ok); + assert_eq!(response.body_string(), Some(FIELD_VALUE.into())); + + let response = client.post("/strict") + .header(ContentType::Form) + .body(format!("field={}&extra=whoops", FIELD_VALUE)) + .dispatch(); + + assert_eq!(response.status(), Status::UnprocessableEntity); + } + + #[test] + fn test_lenient_form() { + let client = client(); + let mut response = client.post("/lenient") + .header(ContentType::Form) + .body(format!("field={}", FIELD_VALUE)) + .dispatch(); + + assert_eq!(response.status(), Status::Ok); + assert_eq!(response.body_string(), Some(FIELD_VALUE.into())); + + let mut response = client.post("/lenient") + .header(ContentType::Form) + .body(format!("field={}&extra=whoops", FIELD_VALUE)) + .dispatch(); + + assert_eq!(response.status(), Status::Ok); + assert_eq!(response.body_string(), Some(FIELD_VALUE.into())); + } +}