blob: a1a7853a280b5ded00c425486af84624e73d063a [file] [log] [blame]
// Copyright (c) 2016 The Rouille developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
//! Parsing data sent with a `<form method="POST">`.
//!
//! In order to parse the body of a request, you can use the `post_input!` macro.
//!
//! ```
//! # #[macro_use] extern crate rouille;
//! use rouille::Request;
//! use rouille::Response;
//!
//! fn handle_request(request: &Request) -> Response {
//! let input = try_or_400!(post_input!(request, {
//! field1: u32,
//! field2: String,
//! }));
//!
//! Response::text(format!("the value of field1 is: {}", input.field1))
//! }
//! # fn main() {}
//! ```
//!
//! In this example, the macro will read the body of the request and try to find fields whose
//! names are `field1` and `field2`. If the body was already retreived earlier, if the content-type
//! is not one of the possible values, or if a field is missing or can't be parsed, then an error
//! is returned. Usually you want to handle this error by returning an error to the client.
//!
//! The macro will define and build a struct whose members are the field names that are passed.
//! The macro then returns a `Result<TheGeneratedStruct, PostError>`.
//!
//! # Data types
//!
//! The types that can be used with this macro are the following:
//!
//! - `String`: The value sent by the client is directly put in the `String`.
//! - `u8`/`i8`/`u16`/`i16`/ `u32`/`i32`/ `u64`/`i64`/`usize`/`isize`/`f32`/`f64`: Rouille will try
//! to parse the number from the data passed by the client. An error is produced if the client
//! sent a value that failed to parse or that overflows the capacity of the number.
//! - `Option<T>`: This is equivalent to `T`, but if the field is missing or fails to parse then
//! the `Option` will contain `None` and no error will be produced.
//! - `bool`: Will be `true` if the field is present at least once and `false` if it is absent.
//! This is suitable to know whether a `<input type="checkbox" />` is checked or not.
//! - `Vec<T>`: Same as `T`, except that if the client sends multiple fields with that name then
//! they are merged together. If you don't use a `Vec` then an error is returned in that
//! situation. If the client provides multiple values and some of them fail to parse, an error
//! is returned. You can use a `Vec<Option<T>>` if you don't want an error on parse failure.
//! Empty vecs are possible.
//! - The file-uploads-related types. See below.
//!
//! > **Note**: You may find resources on the web telling you that you must put brackets (`[` `]`)
//! > after the name of inputs of type `<select multiple>` and `<input type="file" multiple>`.
//! > This is only necessary for some programming languages and frameworks, and is not relevant
//! > for rouille. With rouille you just need to use a `Vec` for the data type.
//!
//! You can also use your own types by implementing the
//! [`DecodePostField` trait](trait.DecodePostField.html). See below.
//!
//! # Handling file uploads
//!
//! In order to receive a file sent with a `<form>`, you should use one of the provided structs
//! that represent a file:
//!
//! - [`BufferedFile`](struct.BufferedFile.html), in which case the body of the file will be stored
//! in memory.
//!
//! Example:
//!
//! ```
//! # #[macro_use] extern crate rouille;
//! use rouille::Request;
//! use rouille::Response;
//! use rouille::input::post::BufferedFile;
//!
//! fn handle_request(request: &Request) -> Response {
//! let input = try_or_400!(post_input!(request, {
//! file: BufferedFile,
//! }));
//!
//! Response::text("everything ok")
//! }
//! # fn main() {}
//! ```
//!
//! # How it works internally
//!
//! In order for the macro to work, each type of data (like `u32`, `String` or `BufferedFile`) must
//! implement the [`DecodePostField` trait](trait.DecodePostField.html).
//!
//! The template parameter of the trait represents the type of the configuration object that is
//! accepted by the methods. If the user doesn't specify any configuration, the type will be `()`.
//!
//! When rouille's parser finds a field with the correct name it will attempt to call the
//! `from_field` method, and if it find a file with the correct name it will attempt to call the
//! `from_file` method. You should return `PostFieldError::WrongFieldType` if you're
//! expecting a file and `from_field` was called, or vice-versa.
use Request;
use std::borrow::Cow;
use std::error;
use std::fmt;
use std::io::BufRead;
use std::io::Error as IoError;
use std::io::Read;
use std::num;
// Must be made public so that it can be used by the `post_input` macro.
#[doc(hidden)]
pub use url::form_urlencoded;
/// Error that can happen when decoding POST data.
#[derive(Debug)]
pub enum PostError {
/// The `Content-Type` header of the request indicates that it doesn't contain POST data.
WrongContentType,
/// Can't parse the body of the request because it was already extracted.
BodyAlreadyExtracted,
/// Could not read the body from the request.
IoError(IoError),
/// Failed to parse a string field.
NotUtf8(String),
/// There was an error with a particular field.
Field {
field: Cow<'static, str>,
error: PostFieldError,
}
}
impl From<IoError> for PostError {
#[inline]
fn from(err: IoError) -> PostError {
PostError::IoError(err)
}
}
impl error::Error for PostError {
#[inline]
fn description(&self) -> &str {
match *self {
PostError::BodyAlreadyExtracted => {
"the body of the request was already extracted"
},
PostError::WrongContentType => {
"the request didn't have a post content type"
},
PostError::IoError(_) => {
"could not read the body from the request, or could not execute the CGI program"
},
PostError::NotUtf8(_) => {
"the content-type encoding is not ASCII or UTF-8, or the body is not valid UTF-8"
},
PostError::Field { .. } => {
"failed to parse a requested field"
},
}
}
#[inline]
fn cause(&self) -> Option<&error::Error> {
match *self {
PostError::IoError(ref e) => Some(e),
PostError::Field { ref error, .. } => Some(error),
_ => None
}
}
}
impl fmt::Display for PostError {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(fmt, "{}", error::Error::description(self))
}
}
/// Error returned by the methods of [the `DecodePostField` trait](trait.DecodePostField.html).
#[derive(Debug)]
pub enum PostFieldError {
/// Could not read the body. Usually happens with files.
IoError(IoError),
/// A field is missing from the received data.
MissingField,
/// Expected a file but got a field, or vice versa.
WrongFieldType,
/// Got multiple values for the same field while only one was expected.
UnexpectedMultipleValues,
/// Failed to parse an integer field.
WrongDataTypeInt(num::ParseIntError),
/// Failed to parse a floating-point field.
WrongDataTypeFloat(num::ParseFloatError),
}
impl From<IoError> for PostFieldError {
#[inline]
fn from(err: IoError) -> PostFieldError {
PostFieldError::IoError(err)
}
}
impl From<num::ParseIntError> for PostFieldError {
#[inline]
fn from(err: num::ParseIntError) -> PostFieldError {
PostFieldError::WrongDataTypeInt(err)
}
}
impl From<num::ParseFloatError> for PostFieldError {
#[inline]
fn from(err: num::ParseFloatError) -> PostFieldError {
PostFieldError::WrongDataTypeFloat(err)
}
}
impl error::Error for PostFieldError {
#[inline]
fn description(&self) -> &str {
match *self {
PostFieldError::IoError(_) => {
"could not read the body from the request, or could not execute the CGI program"
},
PostFieldError::MissingField => {
"the field is missing from the request's client"
},
PostFieldError::WrongFieldType => {
"expected a file but got a field, or vice versa"
},
PostFieldError::UnexpectedMultipleValues => {
"got multiple values for the same field while only one was expected"
},
PostFieldError::WrongDataTypeInt(_) => {
"failed to parse an integer field"
},
PostFieldError::WrongDataTypeFloat(_) => {
"failed to parse a floating-point field"
},
}
}
#[inline]
fn cause(&self) -> Option<&error::Error> {
match *self {
PostFieldError::IoError(ref e) => Some(e),
PostFieldError::WrongDataTypeInt(ref e) => Some(e),
PostFieldError::WrongDataTypeFloat(ref e) => Some(e),
_ => None
}
}
}
impl fmt::Display for PostFieldError {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(fmt, "{}", error::Error::description(self))
}
}
/// Must be implemented on types used with the `post_input!` macro.
///
/// The template parameter represents the type of a configuration object that can be passed by
/// the user when the macro is called. If the user doesn't pass any configuration, the expected
/// type is `()`.
pub trait DecodePostField<Config>: fmt::Debug {
/// Called when a field with the given name is found in the POST input.
///
/// The value of `content` is what the client sent. This function should attempt to parse it
/// into `Self` or return an error if it couldn't. If `Self` can't handle a field, then a
/// `PostFieldError::WrongFieldType` error should be returned.
fn from_field(config: Config, content: &str) -> Result<Self, PostFieldError>
where Self: Sized;
/// Called when a file with the given name is found in the POST input.
///
/// The `file` is an object from which the body of the file can be read. The `filename` and
/// `mime` are also arbitrary values sent directly by the client, so you shouldn't trust them
/// blindly.
///
/// > **Note**: The `file` object can typically read directly from the socket. But don't worry
/// > about doing something wrong, as there are protection mechanisms that will prevent you
/// > from reading too far.
///
/// This method should do something with the file (like storing it somewhere) and return a
/// `Self` that will allow the user to manipulate the file that was uploaded.
///
/// If `Self` can't handle a file, then a `PostFieldError::WrongFieldType` error should
/// be returned.
fn from_file<R>(config: Config, file: R, filename: Option<&str>, mime: &str)
-> Result<Self, PostFieldError>
where Self: Sized,
R: BufRead;
/// When multiple fields with the same name are found in the client's input, rouille will build
/// an object for each of them and then merge them with this method.
///
/// The default implementation returns `UnexpectedMultipleValues`.
fn merge_multiple(self, _existing: Self) -> Result<Self, PostFieldError> where Self: Sized {
Err(PostFieldError::UnexpectedMultipleValues)
}
/// Called when no field is found in the POST input.
///
/// The default implementation returns `MissingField`.
#[inline]
fn not_found(_: Config) -> Result<Self, PostFieldError> where Self: Sized {
Err(PostFieldError::MissingField)
}
}
macro_rules! impl_decode_post_field_decode {
($t:ident) => {
impl DecodePostField<()> for $t {
fn from_field(_: (), content: &str) -> Result<Self, PostFieldError> {
Ok(match content.parse() {
Ok(v) => v,
Err(err) => return Err(err.into())
})
}
fn from_file<R>(_: (), _: R, _: Option<&str>, _: &str)
-> Result<Self, PostFieldError>
where R: BufRead
{
Err(PostFieldError::WrongFieldType)
}
}
}
}
impl_decode_post_field_decode!(u8);
impl_decode_post_field_decode!(i8);
impl_decode_post_field_decode!(u16);
impl_decode_post_field_decode!(i16);
impl_decode_post_field_decode!(u32);
impl_decode_post_field_decode!(i32);
impl_decode_post_field_decode!(u64);
impl_decode_post_field_decode!(i64);
impl_decode_post_field_decode!(usize);
impl_decode_post_field_decode!(isize);
impl_decode_post_field_decode!(f32);
impl_decode_post_field_decode!(f64);
impl DecodePostField<()> for String {
fn from_field(_: (), content: &str) -> Result<Self, PostFieldError> {
Ok(content.to_owned())
}
fn from_file<R>(_: (), _: R, _: Option<&str>, _: &str)
-> Result<Self, PostFieldError>
where R: BufRead
{
Err(PostFieldError::WrongFieldType)
}
}
impl<T, C> DecodePostField<C> for Option<T> where T: DecodePostField<C> {
fn from_field(config: C, content: &str) -> Result<Self, PostFieldError> {
match DecodePostField::from_field(config, content) {
Ok(val) => Ok(Some(val)),
Err(_) => Ok(None)
}
}
fn from_file<R>(config: C, file: R, filename: Option<&str>, mime: &str)
-> Result<Self, PostFieldError>
where R: BufRead
{
match DecodePostField::from_file(config, file, filename, mime) {
Ok(val) => Ok(Some(val)),
Err(_) => Ok(None)
}
}
#[inline]
fn not_found(_: C) -> Result<Self, PostFieldError> {
Ok(None)
}
}
impl DecodePostField<()> for bool {
#[inline]
fn from_field(_: (), _: &str) -> Result<Self, PostFieldError> {
Ok(true)
}
#[inline]
fn from_file<R>(_: (), _: R, _: Option<&str>, _: &str) -> Result<Self, PostFieldError>
where R: BufRead
{
Ok(true)
}
#[inline]
fn merge_multiple(self, existing: bool) -> Result<bool, PostFieldError> {
Ok(self || existing)
}
#[inline]
fn not_found(_: ()) -> Result<Self, PostFieldError> {
Ok(false)
}
}
impl<T, C> DecodePostField<C> for Vec<T> where T: DecodePostField<C> {
fn from_field(config: C, content: &str) -> Result<Self, PostFieldError> {
Ok(vec![try!(DecodePostField::from_field(config, content))])
}
fn from_file<R>(config: C, file: R, filename: Option<&str>, mime: &str)
-> Result<Self, PostFieldError>
where R: BufRead
{
Ok(vec![try!(DecodePostField::from_file(config, file, filename, mime))])
}
fn merge_multiple(mut self, mut existing: Vec<T>) -> Result<Vec<T>, PostFieldError> {
self.append(&mut existing);
Ok(self)
}
#[inline]
fn not_found(_: C) -> Result<Self, PostFieldError> {
Ok(Vec::new())
}
}
/// Implementation of the `DecodePostField` that puts the body of the file in memory.
#[derive(Clone)]
pub struct BufferedFile {
/// The file's data.
pub data: Vec<u8>,
/// The MIME type. Remember that this shouldn't be blindly trusted.
pub mime: String,
/// The name of the file, if known. Remember that this shouldn't be blindly trusted.
pub filename: Option<String>,
}
impl fmt::Debug for BufferedFile {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
fmt.debug_struct("BufferedFile")
.field("data", &format!("<{} bytes>", self.data.len()))
.field("mime", &self.mime)
.field("filename", &self.filename)
.finish()
}
}
impl DecodePostField<()> for BufferedFile {
fn from_field(_: (), _: &str) -> Result<Self, PostFieldError> {
Err(PostFieldError::WrongFieldType)
}
fn from_file<R>(_: (), mut file: R, filename: Option<&str>, mime: &str)
-> Result<Self, PostFieldError>
where R: BufRead
{
let mut out = Vec::new();
try!(file.read_to_end(&mut out));
Ok(BufferedFile {
data: out,
mime: mime.to_owned(),
filename: filename.map(|n| n.to_owned()),
})
}
}
/// Parse input from HTML forms. See [the `post` module](input/post/index.html) for general
/// documentation.
#[macro_export]
macro_rules! post_input {
($request:expr, {$($field:ident: $ty:ty $({$config:expr})*),*$(,)*}) => ({
use std::io::Read;
use std::mem;
use std::result::Result;
use $crate::Request;
use $crate::input::post::DecodePostField;
use $crate::input::post::PostFieldError;
use $crate::input::post::PostError;
use $crate::input::post::form_urlencoded;
use $crate::input::multipart;
#[derive(Debug)]
struct PostInput {
$(
$field: $ty,
)*
}
fn merge<C, T: DecodePostField<C>>(existing: &mut Option<T>, new: T)
-> Result<(), PostFieldError>
{
match existing {
a @ &mut Some(_) => {
let extracted = mem::replace(a, None).unwrap();
let merged = try!(extracted.merge_multiple(new));
*a = Some(merged);
},
a @ &mut None => *a = Some(new),
};
Ok(())
}
fn go(request: &Request) -> Result<PostInput, PostError> {
$(
let mut $field: Option<$ty> = None;
)*
// TODO: handle if the same field is specified multiple times
if request.header("Content-Type").map(|ct| ct.starts_with("application/x-www-form-urlencoded")).unwrap_or(false) {
let body = {
// TODO: DDoSable server if body is too large?
let mut out = Vec::new(); // TODO: with_capacity()?
if let Some(mut b) = request.data() {
try!(b.read_to_end(&mut out));
} else {
return Err(PostError::BodyAlreadyExtracted);
}
out
};
for (field, value) in form_urlencoded::parse(&body) {
$(
if field == stringify!($field) {
let config = ();
$(
let config = $config;
)*
let decoded = match DecodePostField::from_field(config, &value) {
Ok(d) => d,
Err(err) => return Err(PostError::Field {
field: stringify!($field).into(),
error: err,
}),
};
match merge(&mut $field, decoded) {
Ok(d) => d,
Err(err) => return Err(PostError::Field {
field: stringify!($field).into(),
error: err,
}),
};
continue;
}
)*
}
} else {
let mut multipart = match multipart::get_multipart_input(request) {
Ok(m) => m,
Err(multipart::MultipartError::WrongContentType) => {
return Err(PostError::WrongContentType);
},
Err(multipart::MultipartError::BodyAlreadyExtracted) => {
return Err(PostError::BodyAlreadyExtracted);
},
};
while let Some(multipart_entry) = multipart.next() {
$(
if multipart_entry.name == stringify!($field) {
let config = ();
$(
let config = $config;
)*
match multipart_entry.data {
multipart::MultipartData::Text(txt) => {
let decoded = match DecodePostField::from_field(config, &txt.text) {
Ok(d) => d,
Err(err) => return Err(PostError::Field {
field: stringify!($field).into(),
error: err,
}),
};
match merge(&mut $field, decoded) {
Ok(d) => d,
Err(err) => return Err(PostError::Field {
field: stringify!($field).into(),
error: err,
}),
};
},
multipart::MultipartData::File(f) => {
let name = f.filename.as_ref().map(|n| n.to_owned());
let name = name.as_ref().map(|n| &n[..]);
let mime = f.content_type.to_string();
let decoded = match DecodePostField::from_file(config, f, name, &mime) {
Ok(d) => d,
Err(err) => return Err(PostError::Field {
field: stringify!($field).into(),
error: err,
}),
};
match merge(&mut $field, decoded) {
Ok(d) => d,
Err(err) => return Err(PostError::Field {
field: stringify!($field).into(),
error: err,
}),
};
},
}
continue;
}
)*
}
}
Ok(PostInput {
$(
$field: match $field {
Some(v) => v,
None => {
let config = ();
$(
let config = $config;
)*
match DecodePostField::not_found(config) {
Ok(d) => d,
Err(err) => return Err(PostError::Field {
field: stringify!($field).into(),
error: err,
}),
}
}
},
)*
})
}
go($request)
});
}
/// Attempts to decode the `POST` data received by the request.
///
/// If successful, returns a list of fields and values.
///
/// Returns an error if the request's content-type is not related to POST data.
// TODO: what to do with this function?
pub fn raw_urlencoded_post_input(request: &Request) -> Result<Vec<(String, String)>, PostError> {
if request.header("Content-Type").map(|ct| !ct.starts_with("application/x-www-form-urlencoded")).unwrap_or(true) {
return Err(PostError::WrongContentType);
}
let body = {
// TODO: DDoSable server if body is too large?
let mut out = Vec::new(); // TODO: with_capacity()?
if let Some(mut b) = request.data() {
try!(b.read_to_end(&mut out));
} else {
return Err(PostError::BodyAlreadyExtracted);
}
out
};
Ok(form_urlencoded::parse(&body).into_owned().collect()) // TODO: suboptimal
}
#[cfg(test)]
mod tests {
use Request;
use input::post::PostError;
use input::post::PostFieldError;
#[test]
fn basic_int() {
let request = Request::fake_http("GET", "/", vec![
("Host".to_owned(), "localhost".to_owned()),
("Content-Type".to_owned(), "application/x-www-form-urlencoded".to_owned())
], b"field=12".to_vec());
let input = post_input!(&request, {
field: u32
}).unwrap();
assert_eq!(input.field, 12);
}
#[test]
fn basic_float() {
let request = Request::fake_http("GET", "/", vec![
("Host".to_owned(), "localhost".to_owned()),
("Content-Type".to_owned(), "application/x-www-form-urlencoded".to_owned())
], b"field=12.8".to_vec());
let input = post_input!(&request, {
field: f32
}).unwrap();
assert_eq!(input.field, 12.8);
}
#[test]
fn basic_string() {
let request = Request::fake_http("GET", "/", vec![
("Host".to_owned(), "localhost".to_owned()),
("Content-Type".to_owned(), "application/x-www-form-urlencoded".to_owned())
], b"field=value".to_vec());
let input = post_input!(&request, {
field: String
}).unwrap();
assert_eq!(input.field, "value");
}
#[test]
fn basic_option_string() {
let request = Request::fake_http("GET", "/", vec![
("Host".to_owned(), "localhost".to_owned()),
("Content-Type".to_owned(), "application/x-www-form-urlencoded".to_owned())
], b"field=value".to_vec());
let input = post_input!(&request, {
field: Option<String>
}).unwrap();
assert_eq!(input.field.unwrap(), "value");
}
#[test]
fn basic_bool() {
let request = Request::fake_http("GET", "/", vec![
("Host".to_owned(), "localhost".to_owned()),
("Content-Type".to_owned(), "application/x-www-form-urlencoded".to_owned())
], b"field=value".to_vec());
let input = post_input!(&request, {
field: bool
}).unwrap();
assert_eq!(input.field, true);
}
#[test]
fn weird_stuff() {
let request = Request::fake_http("GET", "/", vec![
("Host".to_owned(), "localhost".to_owned()),
("Content-Type".to_owned(), "application/x-www-form-urlencoded".to_owned())
], b"&=&aa&b=&c=c=c&field=value&".to_vec());
let input = post_input!(&request, {
field: String
}).unwrap();
assert_eq!(input.field, "value");
}
#[test]
fn wrong_content_type() {
let request = Request::fake_http("GET", "/", vec![
("Host".to_owned(), "localhost".to_owned()),
("Content-Type".to_owned(), "wrong".to_owned())
], b"field=value".to_vec());
let input = post_input!(&request, {
field: String
});
match input {
Err(PostError::WrongContentType) => (),
_ => panic!()
}
}
#[test]
fn too_many_fields() {
let request = Request::fake_http("GET", "/", vec![
("Host".to_owned(), "localhost".to_owned()),
("Content-Type".to_owned(), "application/x-www-form-urlencoded".to_owned())
], b"field=12&field2=58".to_vec());
let input = post_input!(&request, {
field: u32
}).unwrap();
assert_eq!(input.field, 12);
}
#[test]
fn multiple_values() {
let request = Request::fake_http("GET", "/", vec![
("Host".to_owned(), "localhost".to_owned()),
("Content-Type".to_owned(), "application/x-www-form-urlencoded".to_owned())
], b"field=12&field=58".to_vec());
let input = post_input!(&request, {
field: u32
});
match input {
Err(PostError::Field { ref field, error: PostFieldError::UnexpectedMultipleValues })
if field == "field" => (),
_ => panic!()
}
}
#[test]
fn multiple_values_bool() {
let request = Request::fake_http("GET", "/", vec![
("Host".to_owned(), "localhost".to_owned()),
("Content-Type".to_owned(), "application/x-www-form-urlencoded".to_owned())
], b"field=12&field=58".to_vec());
let input = post_input!(&request, {
field: bool
}).unwrap();
assert_eq!(input.field, true);
}
#[test]
fn multiple_values_vec() {
let request = Request::fake_http("GET", "/", vec![
("Host".to_owned(), "localhost".to_owned()),
("Content-Type".to_owned(), "application/x-www-form-urlencoded".to_owned())
], b"field=12&field=58".to_vec());
let input = post_input!(&request, {
field: Vec<u32>
}).unwrap();
assert_eq!(input.field, &[12, 58]);
}
#[test]
fn multiple_values_vec_parse_failure() {
let request = Request::fake_http("GET", "/", vec![
("Host".to_owned(), "localhost".to_owned()),
("Content-Type".to_owned(), "application/x-www-form-urlencoded".to_owned())
], b"field=12&field=800".to_vec());
let input = post_input!(&request, {
field: Vec<u8>
});
match input {
Err(PostError::Field { ref field, error: PostFieldError::WrongDataTypeInt(_) })
if field == "field" => (),
_ => panic!()
}
}
#[test]
fn multiple_values_vec_option_parse_failure() {
let request = Request::fake_http("GET", "/", vec![
("Host".to_owned(), "localhost".to_owned()),
("Content-Type".to_owned(), "application/x-www-form-urlencoded".to_owned())
], b"field=12&field=800".to_vec());
let input = post_input!(&request, {
field: Vec<Option<u8>>
}).unwrap();
assert_eq!(input.field, &[Some(12), None]);
}
#[test]
fn missing_field() {
let request = Request::fake_http("GET", "/", vec![
("Host".to_owned(), "localhost".to_owned()),
("Content-Type".to_owned(), "application/x-www-form-urlencoded".to_owned())
], b"wrong_field=value".to_vec());
let input = post_input!(&request, {
field: String
});
match input {
Err(PostError::Field { ref field, error: PostFieldError::MissingField })
if field == "field" => (),
_ => panic!()
}
}
#[test]
fn missing_field_option() {
let request = Request::fake_http("GET", "/", vec![
("Host".to_owned(), "localhost".to_owned()),
("Content-Type".to_owned(), "application/x-www-form-urlencoded".to_owned())
], b"wrong=value".to_vec());
let input = post_input!(&request, {
field: Option<String>
}).unwrap();
assert_eq!(input.field, None);
}
#[test]
fn missing_field_bool() {
let request = Request::fake_http("GET", "/", vec![
("Host".to_owned(), "localhost".to_owned()),
("Content-Type".to_owned(), "application/x-www-form-urlencoded".to_owned())
], b"wrong=value".to_vec());
let input = post_input!(&request, {
field: bool
}).unwrap();
assert_eq!(input.field, false);
}
#[test]
fn missing_field_vec() {
let request = Request::fake_http("GET", "/", vec![
("Host".to_owned(), "localhost".to_owned()),
("Content-Type".to_owned(), "application/x-www-form-urlencoded".to_owned())
], b"wrong=value".to_vec());
let input = post_input!(&request, {
field: Vec<String>
}).unwrap();
assert!(input.field.is_empty());
}
#[test]
fn num_parse_error() {
let request = Request::fake_http("GET", "/", vec![
("Host".to_owned(), "localhost".to_owned()),
("Content-Type".to_owned(), "application/x-www-form-urlencoded".to_owned())
], b"field=12foo".to_vec());
let input = post_input!(&request, {
field: u32
});
match input {
Err(PostError::Field { ref field, error: PostFieldError::WrongDataTypeInt(_) })
if field == "field" => (),
_ => panic!()
}
}
#[test]
fn num_parse_error_option() {
let request = Request::fake_http("GET", "/", vec![
("Host".to_owned(), "localhost".to_owned()),
("Content-Type".to_owned(), "application/x-www-form-urlencoded".to_owned())
], b"field=12foo".to_vec());
let input = post_input!(&request, {
field: Option<u32>
}).unwrap();
assert_eq!(input.field, None);
}
#[test]
fn num_overflow() {
let request = Request::fake_http("GET", "/", vec![
("Host".to_owned(), "localhost".to_owned()),
("Content-Type".to_owned(), "application/x-www-form-urlencoded".to_owned())
], b"field=800".to_vec());
let input = post_input!(&request, {
field: u8
});
match input {
Err(PostError::Field { ref field, error: PostFieldError::WrongDataTypeInt(_) })
if field == "field" => (),
_ => panic!()
}
}
#[test]
fn body_extracted() {
let request = Request::fake_http("GET", "/", vec![
("Host".to_owned(), "localhost".to_owned()),
("Content-Type".to_owned(), "application/x-www-form-urlencoded".to_owned())
], b"field=800".to_vec());
let _ = request.data();
let input = post_input!(&request, {
field: u8
});
match input {
Err(PostError::BodyAlreadyExtracted) => (),
_ => panic!()
}
}
#[test]
#[ignore] // FIXME:
fn not_utf8() {
let request = Request::fake_http("GET", "/", vec![
("Host".to_owned(), "localhost".to_owned()),
("Content-Type".to_owned(), "application/x-www-form-urlencoded".to_owned())
], b"field=\xc3\x28".to_vec());
let input = post_input!(&request, {
field: String
});
match input {
Err(PostError::NotUtf8(_)) => (),
v => panic!("{:?}", v)
}
}
// TODO: add tests for multipart/form-data
}