mirror of https://github.com/rwf2/Rocket.git
Add support for lenient forms via 'LenientForm'.
This commit changes the 'FromForm' trait in two ways: 1. The singular method is now named 'from_form'. 2. The method takes a second parameter: 'strict: bool'. The 'strict' parameter is used to specify whether form parsing should be strict or not (i.e. lenient). When parsing is lenient, extra form fields do not result in an error. This lenient behavior is used by a new 'LenientForm' data guard type to request lenient form parsing. The behavior for 'Form' remains unchanged. Resolves #242.
This commit is contained in:
parent
8536e39d10
commit
a3ea9d0f9a
|
@ -79,7 +79,7 @@ pub fn from_form_derive(ecx: &mut ExtCtxt, span: Span, meta_item: &MetaItem,
|
||||||
generics: trait_generics,
|
generics: trait_generics,
|
||||||
methods: vec![
|
methods: vec![
|
||||||
MethodDef {
|
MethodDef {
|
||||||
name: "from_form_items",
|
name: "from_form",
|
||||||
generics: ty::LifetimeBounds::empty(),
|
generics: ty::LifetimeBounds::empty(),
|
||||||
explicit_self: None,
|
explicit_self: None,
|
||||||
args: vec![
|
args: vec![
|
||||||
|
@ -91,10 +91,15 @@ pub fn from_form_derive(ecx: &mut ExtCtxt, span: Span, meta_item: &MetaItem,
|
||||||
global: true
|
global: true
|
||||||
})),
|
})),
|
||||||
ty::Borrowed(None, Mutability::Mutable)
|
ty::Borrowed(None, Mutability::Mutable)
|
||||||
)
|
),
|
||||||
|
ty::Literal(ty::Path {
|
||||||
|
path: vec!["bool"],
|
||||||
|
lifetime: None,
|
||||||
|
params: vec![],
|
||||||
|
global: false,
|
||||||
|
})
|
||||||
],
|
],
|
||||||
ret_ty: ty::Ty::Literal(
|
ret_ty: ty::Literal(ty::Path {
|
||||||
ty::Path {
|
|
||||||
path: vec!["std", "result", "Result"],
|
path: vec!["std", "result", "Result"],
|
||||||
lifetime: None,
|
lifetime: None,
|
||||||
params: vec![
|
params: vec![
|
||||||
|
@ -102,8 +107,7 @@ pub fn from_form_derive(ecx: &mut ExtCtxt, span: Span, meta_item: &MetaItem,
|
||||||
Box::new(error_type.clone())
|
Box::new(error_type.clone())
|
||||||
],
|
],
|
||||||
global: true,
|
global: true,
|
||||||
}
|
}),
|
||||||
),
|
|
||||||
attributes: vec![],
|
attributes: vec![],
|
||||||
is_unsafe: false,
|
is_unsafe: false,
|
||||||
combine_substructure: c_s(Box::new(from_form_substructure)),
|
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<Expr> {
|
fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substructure) -> P<Expr> {
|
||||||
// Check that we specified the methods to the argument correctly.
|
// Check that we specified the methods to the argument correctly.
|
||||||
const EXPECTED_ARGS: usize = 1;
|
const EXPECTED_ARGS: usize = 2;
|
||||||
let arg = if substr.nonself_args.len() == EXPECTED_ARGS {
|
let (items_arg, strict_arg) = if substr.nonself_args.len() == EXPECTED_ARGS {
|
||||||
&substr.nonself_args[0]
|
(&substr.nonself_args[0], &substr.nonself_args[1])
|
||||||
} else {
|
} else {
|
||||||
let msg = format!("incorrect number of arguments in `from_form_string`: \
|
let msg = format!("incorrect number of arguments in `from_form_string`: \
|
||||||
expected {}, found {}", EXPECTED_ARGS, substr.nonself_args.len());
|
expected {}, found {}", EXPECTED_ARGS, substr.nonself_args.len());
|
||||||
cx.span_bug(trait_span, msg.as_str());
|
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.
|
// Ensure the the fields are from a 'StaticStruct' and extract them.
|
||||||
let fields = match *substr.fields {
|
let fields = match *substr.fields {
|
||||||
|
@ -251,19 +255,18 @@ 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
|
// The actual match statement. Iterate through all of the fields in the form
|
||||||
// and use the $arms generated above.
|
// and use the $arms generated above.
|
||||||
stmts.push(quote_stmt!(cx,
|
stmts.push(quote_stmt!(cx,
|
||||||
for (__k, __v) in $arg {
|
for (__k, __v) in $items_arg {
|
||||||
match __k.as_str() {
|
match __k.as_str() {
|
||||||
$arms
|
$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. */
|
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
|
// 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.",
|
println!(" => {}={} has no matching field in struct.",
|
||||||
__k, __v);
|
__k, __v);
|
||||||
$return_err_stmt
|
$return_err_stmt
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
).unwrap());
|
).unwrap());
|
||||||
|
|
|
@ -76,7 +76,8 @@ impl RouteGenerateExt for RouteParams {
|
||||||
Some(quote_stmt!(ecx,
|
Some(quote_stmt!(ecx,
|
||||||
let $name: $ty = {
|
let $name: $ty = {
|
||||||
let mut items = ::rocket::request::FormItems::from($form_string);
|
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,
|
Ok(v) => v,
|
||||||
Err(_) => return ::rocket::Outcome::Forward(__data)
|
Err(_) => return ::rocket::Outcome::Forward(__data)
|
||||||
};
|
};
|
||||||
|
|
|
@ -70,9 +70,9 @@ struct FieldNamedV<'r> {
|
||||||
v: &'r RawStr,
|
v: &'r RawStr,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse<'f, T: FromForm<'f>>(string: &'f str) -> Option<T> {
|
fn parse<'f, T: FromForm<'f>>(string: &'f str, strict: bool) -> Option<T> {
|
||||||
let mut items = FormItems::from(string);
|
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() {
|
if !items.exhaust() {
|
||||||
panic!("Invalid form input.");
|
panic!("Invalid form input.");
|
||||||
}
|
}
|
||||||
|
@ -80,20 +80,28 @@ fn parse<'f, T: FromForm<'f>>(string: &'f str) -> Option<T> {
|
||||||
result.ok()
|
result.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn strict<'f, T: FromForm<'f>>(string: &'f str) -> Option<T> {
|
||||||
|
parse(string, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lenient<'f, T: FromForm<'f>>(string: &'f str) -> Option<T> {
|
||||||
|
parse(string, false)
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Same number of arguments: simple case.
|
// Same number of arguments: simple case.
|
||||||
let task: Option<TodoTask> = parse("description=Hello&completed=on");
|
let task: Option<TodoTask> = strict("description=Hello&completed=on");
|
||||||
assert_eq!(task, Some(TodoTask {
|
assert_eq!(task, Some(TodoTask {
|
||||||
description: "Hello".to_string(),
|
description: "Hello".to_string(),
|
||||||
completed: true
|
completed: true
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Argument in string but not in form.
|
// Argument in string but not in form.
|
||||||
let task: Option<TodoTask> = parse("other=a&description=Hello&completed=on");
|
let task: Option<TodoTask> = strict("other=a&description=Hello&completed=on");
|
||||||
assert!(task.is_none());
|
assert!(task.is_none());
|
||||||
|
|
||||||
// Ensure _method isn't required.
|
// Ensure _method isn't required.
|
||||||
let task: Option<TodoTask> = parse("_method=patch&description=Hello&completed=off");
|
let task: Option<TodoTask> = strict("_method=patch&description=Hello&completed=off");
|
||||||
assert_eq!(task, Some(TodoTask {
|
assert_eq!(task, Some(TodoTask {
|
||||||
description: "Hello".to_string(),
|
description: "Hello".to_string(),
|
||||||
completed: false
|
completed: false
|
||||||
|
@ -104,7 +112,7 @@ fn main() {
|
||||||
"checkbox=off", "textarea=", "select=a", "radio=c",
|
"checkbox=off", "textarea=", "select=a", "radio=c",
|
||||||
].join("&");
|
].join("&");
|
||||||
|
|
||||||
let input: Option<FormInput> = parse(&form_string);
|
let input: Option<FormInput> = strict(&form_string);
|
||||||
assert_eq!(input, Some(FormInput {
|
assert_eq!(input, Some(FormInput {
|
||||||
checkbox: false,
|
checkbox: false,
|
||||||
number: 10,
|
number: 10,
|
||||||
|
@ -115,41 +123,77 @@ fn main() {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Argument not in string with default in form.
|
// Argument not in string with default in form.
|
||||||
let default: Option<DefaultInput> = parse("");
|
let default: Option<DefaultInput> = strict("");
|
||||||
assert_eq!(default, Some(DefaultInput {
|
assert_eq!(default, Some(DefaultInput {
|
||||||
arg: None
|
arg: None
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Ensure _method can be captured if desired.
|
// Ensure _method can be captured if desired.
|
||||||
let manual: Option<ManualMethod> = parse("_method=put&done=true");
|
let manual: Option<ManualMethod> = strict("_method=put&done=true");
|
||||||
|
assert_eq!(manual, Some(ManualMethod {
|
||||||
|
_method: Some("put".into()),
|
||||||
|
done: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
let manual: Option<ManualMethod> = lenient("_method=put&done=true");
|
||||||
assert_eq!(manual, Some(ManualMethod {
|
assert_eq!(manual, Some(ManualMethod {
|
||||||
_method: Some("put".into()),
|
_method: Some("put".into()),
|
||||||
done: true
|
done: true
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// And ignored when not present.
|
// And ignored when not present.
|
||||||
let manual: Option<ManualMethod> = parse("done=true");
|
let manual: Option<ManualMethod> = strict("done=true");
|
||||||
assert_eq!(manual, Some(ManualMethod {
|
assert_eq!(manual, Some(ManualMethod {
|
||||||
_method: None,
|
_method: None,
|
||||||
done: true
|
done: true
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Check that a `bool` value that isn't in the form is marked as `false`.
|
// Check that a `bool` value that isn't in the form is marked as `false`.
|
||||||
let manual: Option<UnpresentCheckbox> = parse("");
|
let manual: Option<UnpresentCheckbox> = strict("");
|
||||||
assert_eq!(manual, Some(UnpresentCheckbox {
|
assert_eq!(manual, Some(UnpresentCheckbox {
|
||||||
checkbox: false
|
checkbox: false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Check that a `bool` value that isn't in the form is marked as `false`.
|
// Check that a `bool` value that isn't in the form is marked as `false`.
|
||||||
let manual: Option<UnpresentCheckboxTwo> = parse("something=hello");
|
let manual: Option<UnpresentCheckboxTwo> = strict("something=hello");
|
||||||
assert_eq!(manual, Some(UnpresentCheckboxTwo {
|
assert_eq!(manual, Some(UnpresentCheckboxTwo {
|
||||||
checkbox: false,
|
checkbox: false,
|
||||||
something: "hello".into()
|
something: "hello".into()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Check that a structure with one field `v` parses correctly.
|
// Check that a structure with one field `v` parses correctly.
|
||||||
let manual: Option<FieldNamedV> = parse("v=abc");
|
let manual: Option<FieldNamedV> = strict("v=abc");
|
||||||
assert_eq!(manual, Some(FieldNamedV {
|
assert_eq!(manual, Some(FieldNamedV {
|
||||||
v: "abc".into()
|
v: "abc".into()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Check that a structure with one field `v` parses correctly (lenient).
|
||||||
|
let manual: Option<FieldNamedV> = lenient("v=abc");
|
||||||
|
assert_eq!(manual, Some(FieldNamedV { v: "abc".into() }));
|
||||||
|
|
||||||
|
let manual: Option<FieldNamedV> = lenient("v=abc&a=123");
|
||||||
|
assert_eq!(manual, Some(FieldNamedV { v: "abc".into() }));
|
||||||
|
|
||||||
|
let manual: Option<FieldNamedV> = 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<UnpresentCheckboxTwo> = lenient("something=hello");
|
||||||
|
assert_eq!(manual, Some(UnpresentCheckboxTwo {
|
||||||
|
checkbox: false,
|
||||||
|
something: "hello".into()
|
||||||
|
}));
|
||||||
|
|
||||||
|
let manual: Option<UnpresentCheckboxTwo> = 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<FieldNamedV> = lenient("a=abc");
|
||||||
|
assert!(manual.is_none());
|
||||||
|
|
||||||
|
let manual: Option<FieldNamedV> = lenient("_method=abc");
|
||||||
|
assert!(manual.is_none());
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,9 @@ struct Form { }
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Same number of arguments: simple case.
|
// 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 { }));
|
assert_eq!(task, Ok(Form { }));
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,9 @@ struct Form {
|
||||||
double: String,
|
double: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse<'f, T: FromForm<'f>>(string: &'f str) -> Option<T> {
|
fn parse<'f, T: FromForm<'f>>(string: &'f str, strict: bool) -> Option<T> {
|
||||||
let mut items = FormItems::from(string);
|
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() {
|
if !items.exhaust() {
|
||||||
panic!("Invalid form input.");
|
panic!("Invalid form input.");
|
||||||
}
|
}
|
||||||
|
@ -29,13 +29,17 @@ fn parse<'f, T: FromForm<'f>>(string: &'f str) -> Option<T> {
|
||||||
result.ok()
|
result.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_strict<'f, T: FromForm<'f>>(string: &'f str) -> Option<T> {
|
||||||
|
parse(string, true)
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let form_string = &[
|
let form_string = &[
|
||||||
"single=100", "camelCase=helloThere", "TitleCase=HiHi", "type=-2",
|
"single=100", "camelCase=helloThere", "TitleCase=HiHi", "type=-2",
|
||||||
"DOUBLE=bing_bong"
|
"DOUBLE=bing_bong"
|
||||||
].join("&");
|
].join("&");
|
||||||
|
|
||||||
let form: Option<Form> = parse(&form_string);
|
let form: Option<Form> = parse_strict(&form_string);
|
||||||
assert_eq!(form, Some(Form {
|
assert_eq!(form, Some(Form {
|
||||||
single: 100,
|
single: 100,
|
||||||
camel_case: "helloThere".into(),
|
camel_case: "helloThere".into(),
|
||||||
|
@ -49,6 +53,6 @@ fn main() {
|
||||||
"DOUBLE=bing_bong"
|
"DOUBLE=bing_bong"
|
||||||
].join("&");
|
].join("&");
|
||||||
|
|
||||||
let form: Option<Form> = parse(&form_string);
|
let form: Option<Form> = parse_strict(&form_string);
|
||||||
assert!(form.is_none());
|
assert!(form.is_none());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<T>` will parse successfully from an incoming form only if the form
|
||||||
|
/// contains the exact set of fields in `T`. Said another way, a `Form<T>` 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<T>`. 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 = "<param>"` 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 = "<param>")]
|
||||||
|
/// fn submit(form: Form<T>) ... { ... }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// 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 = "<user_input>")]
|
||||||
|
/// 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 = "<user_input>")]
|
||||||
|
/// fn submit_task(user_input: Form<OwnedUserInput>) -> 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<T, E> {
|
||||||
|
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 = "<form>")]
|
||||||
|
/// fn submit(form: Form<MyForm>) -> 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 = "<form>")]
|
||||||
|
/// fn submit(form: Form<MyForm>) -> 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<Self, T::Error> {
|
||||||
|
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 = "<form>")]
|
||||||
|
/// fn submit(form: Form<MyForm>) -> 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<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.
|
||||||
|
#[inline]
|
||||||
|
fn from_data(request: &Request, data: Data) -> data::Outcome<Self, Self::Error> {
|
||||||
|
super::from_data(request, data, true)
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,16 +52,16 @@ pub trait FromForm<'f>: Sized {
|
||||||
/// The associated error to be returned when parsing fails.
|
/// The associated error to be returned when parsing fails.
|
||||||
type Error;
|
type Error;
|
||||||
|
|
||||||
/// Parses an instance of `Self` from the form items or returns an `Error`
|
/// Parses an instance of `Self` from the iterator of form items `it` or
|
||||||
/// if one cannot be parsed.
|
/// returns an instance of `Self::Error` if one cannot be parsed.
|
||||||
fn from_form_items(form_items: &mut FormItems<'f>) -> Result<Self, Self::Error>;
|
fn from_form(it: &mut FormItems<'f>, strict: bool) -> Result<Self, Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This implementation should only be used during debugging!
|
/// This implementation should only be used during debugging!
|
||||||
impl<'f> FromForm<'f> for &'f str {
|
impl<'f> FromForm<'f> for &'f str {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn from_form_items(items: &mut FormItems<'f>) -> Result<Self, Self::Error> {
|
fn from_form(items: &mut FormItems<'f>, _: bool) -> Result<Self, Self::Error> {
|
||||||
items.mark_complete();
|
items.mark_complete();
|
||||||
Ok(items.inner_str())
|
Ok(items.inner_str())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<T>` will parse successfully from an incoming form if the form
|
||||||
|
/// contains a superset of the fields in `T`. Said another way, a
|
||||||
|
/// `LenientForm<T>` 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<T>`.
|
||||||
|
///
|
||||||
|
/// # 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 = "<param>"` 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 = "<param>")]
|
||||||
|
/// fn submit(form: LenientForm<T>) ... { ... }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## 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 = "<form>")]
|
||||||
|
/// fn submit(form: LenientForm<MyForm>) -> 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 = "<form>")]
|
||||||
|
/// fn submit(form: LenientForm<MyForm>) -> 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 = "<form>")]
|
||||||
|
/// fn submit(form: LenientForm<MyForm>) -> 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<String>;
|
||||||
|
|
||||||
|
/// 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<Self, Self::Error> {
|
||||||
|
super::from_data(request, data, false).map(|form| LenientForm(form))
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,287 +19,44 @@
|
||||||
mod form_items;
|
mod form_items;
|
||||||
mod from_form;
|
mod from_form;
|
||||||
mod from_form_value;
|
mod from_form_value;
|
||||||
|
mod form;
|
||||||
|
mod lenient;
|
||||||
|
|
||||||
pub use self::form_items::FormItems;
|
pub use self::form_items::FormItems;
|
||||||
pub use self::from_form::FromForm;
|
pub use self::from_form::FromForm;
|
||||||
pub use self::from_form_value::FromFormValue;
|
pub use self::from_form_value::FromFormValue;
|
||||||
|
pub use self::form::Form;
|
||||||
|
pub use self::lenient::LenientForm;
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
use std::cmp;
|
||||||
use std::fmt::{self, Debug};
|
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use http::Status;
|
|
||||||
use request::Request;
|
|
||||||
use data::{self, Data, FromData};
|
|
||||||
use outcome::Outcome::*;
|
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.
|
fn from_data<'f, T>(request: &Request,
|
||||||
/// A `FromData` type for parsing `FromForm` types.
|
data: Data,
|
||||||
///
|
strict: bool
|
||||||
/// This type implements the `FromData` trait. It provides a generic means to
|
) -> data::Outcome<Form<'f, T>, Option<String>>
|
||||||
/// parse arbitrary structures from incoming form data.
|
where T: FromForm<'f>, T::Error: Debug
|
||||||
///
|
{
|
||||||
/// # 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 = "<param>"` 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 = "<param>")]
|
|
||||||
/// fn submit(form: Form<T>) ... { ... }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// 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 = "<user_input>")]
|
|
||||||
/// 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 = "<user_input>")]
|
|
||||||
/// fn submit_task(user_input: Form<OwnedUserInput>) -> 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<T, E> {
|
|
||||||
Ok(T),
|
|
||||||
Err(String, E),
|
|
||||||
Invalid(String)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
impl<T, E> FormResult<T, E> {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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<Self, T::Error> {
|
|
||||||
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<String>;
|
|
||||||
|
|
||||||
fn from_data(request: &Request, data: Data) -> data::Outcome<Self, Self::Error> {
|
|
||||||
if !request.content_type().map_or(false, |ct| ct.is_form()) {
|
if !request.content_type().map_or(false, |ct| ct.is_form()) {
|
||||||
warn_!("Form data does not have form content type.");
|
warn_!("Form data does not have form content type.");
|
||||||
return Forward(data);
|
return Forward(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut form_string = String::with_capacity(4096);
|
|
||||||
let limit = request.limits().forms;
|
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);
|
let mut stream = data.open().take(limit);
|
||||||
if let Err(e) = stream.read_to_string(&mut form_string) {
|
if let Err(e) = stream.read_to_string(&mut form_string) {
|
||||||
error_!("IO Error: {:?}", e);
|
error_!("IO Error: {:?}", e);
|
||||||
Failure((Status::InternalServerError, None))
|
Failure((Status::InternalServerError, None))
|
||||||
} else {
|
} else {
|
||||||
match Form::new(form_string) {
|
match Form::new(form_string, strict) {
|
||||||
FormResult::Ok(form) => Success(form),
|
FormResult::Ok(form) => Success(form),
|
||||||
FormResult::Invalid(form_string) => {
|
FormResult::Invalid(form_string) => {
|
||||||
error_!("The request's form string was malformed.");
|
error_!("The request's form string was malformed.");
|
||||||
|
@ -312,13 +69,21 @@ impl<'f, T: FromForm<'f>> FromData for Form<'f, T> where T::Error: Debug {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::Form;
|
use super::{Form, FormResult};
|
||||||
use ::request::{FromForm, FormItems};
|
use ::request::{FromForm, FormItems};
|
||||||
|
|
||||||
|
impl<T, E> FormResult<T, E> {
|
||||||
|
fn unwrap(self) -> T {
|
||||||
|
match self {
|
||||||
|
FormResult::Ok(val) => val,
|
||||||
|
_ => panic!("Unwrapping non-Ok FormResult.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Simple<'s> {
|
struct Simple<'s> {
|
||||||
value: &'s str
|
value: &'s str
|
||||||
}
|
}
|
||||||
|
@ -330,7 +95,7 @@ mod test {
|
||||||
impl<'s> FromForm<'s> for Simple<'s> {
|
impl<'s> FromForm<'s> for Simple<'s> {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn from_form_items(items: &mut FormItems<'s>) -> Result<Simple<'s>, ()> {
|
fn from_form(items: &mut FormItems<'s>, _: bool) -> Result<Simple<'s>, ()> {
|
||||||
Ok(Simple { value: items.inner_str() })
|
Ok(Simple { value: items.inner_str() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -338,7 +103,7 @@ mod test {
|
||||||
impl<'s> FromForm<'s> for Other {
|
impl<'s> FromForm<'s> for Other {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn from_form_items(items: &mut FormItems<'s>) -> Result<Other, ()> {
|
fn from_form(items: &mut FormItems<'s>, _: bool) -> Result<Other, ()> {
|
||||||
Ok(Other { value: items.inner_str().to_string() })
|
Ok(Other { value: items.inner_str().to_string() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -346,7 +111,7 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lifetime() {
|
fn test_lifetime() {
|
||||||
let form_string = "hello=world".to_string();
|
let form_string = "hello=world".to_string();
|
||||||
let form: Form<Simple> = Form::new(form_string).unwrap();
|
let form: Form<Simple> = Form::new(form_string, true).unwrap();
|
||||||
|
|
||||||
let string: &str = form.get().value;
|
let string: &str = form.get().value;
|
||||||
assert_eq!(string, "hello=world");
|
assert_eq!(string, "hello=world");
|
||||||
|
@ -356,7 +121,7 @@ mod test {
|
||||||
fn test_lifetime_2() {
|
fn test_lifetime_2() {
|
||||||
let form_string = "hello=world".to_string();
|
let form_string = "hello=world".to_string();
|
||||||
let mut _y = "hi";
|
let mut _y = "hi";
|
||||||
let _form: Form<Simple> = Form::new(form_string).unwrap();
|
let _form: Form<Simple> = Form::new(form_string, true).unwrap();
|
||||||
// _y = form.get().value;
|
// _y = form.get().value;
|
||||||
|
|
||||||
// fn should_not_compile<'f>(form: Form<'f, &'f str>) -> &'f str {
|
// fn should_not_compile<'f>(form: Form<'f, &'f str>) -> &'f str {
|
||||||
|
@ -373,7 +138,7 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lifetime_3() {
|
fn test_lifetime_3() {
|
||||||
let form_string = "hello=world".to_string();
|
let form_string = "hello=world".to_string();
|
||||||
let form: Form<Other> = Form::new(form_string).unwrap();
|
let form: Form<Other> = Form::new(form_string, true).unwrap();
|
||||||
|
|
||||||
// Not bad.
|
// Not bad.
|
||||||
fn should_compile(form: Form<Other>) -> String {
|
fn should_compile(form: Form<Other>) -> String {
|
||||||
|
@ -386,7 +151,7 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lifetime_4() {
|
fn test_lifetime_4() {
|
||||||
let form_string = "hello=world".to_string();
|
let form_string = "hello=world".to_string();
|
||||||
let form: Form<Simple> = Form::new(form_string).unwrap();
|
let form: Form<Simple> = Form::new(form_string, true).unwrap();
|
||||||
|
|
||||||
fn should_compile<'f>(_form: Form<'f, Simple<'f>>) { }
|
fn should_compile<'f>(_form: Form<'f, Simple<'f>>) { }
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ mod tests;
|
||||||
pub use self::request::Request;
|
pub use self::request::Request;
|
||||||
pub use self::from_request::{FromRequest, Outcome};
|
pub use self::from_request::{FromRequest, Outcome};
|
||||||
pub use self::param::{FromParam, FromSegments};
|
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;
|
pub use self::state::State;
|
||||||
|
|
||||||
/// Type alias to retrieve [Flash](/rocket/response/struct.Flash.html) messages
|
/// Type alias to retrieve [Flash](/rocket/response/struct.Flash.html) messages
|
||||||
|
|
|
@ -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 = "<form>")]
|
||||||
|
fn strict<'r>(form: Form<'r, MyForm<'r>>) -> String {
|
||||||
|
form.get().field.as_str().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/lenient", data = "<form>")]
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue