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;
#[doc(hidden)]
pub use url::form_urlencoded;
#[derive(Debug)]
pub enum PostError {
WrongContentType,
BodyAlreadyExtracted,
IoError(IoError),
NotUtf8(String),
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))
}
}
#[derive(Debug)]
pub enum PostFieldError {
IoError(IoError),
MissingField,
WrongFieldType,
UnexpectedMultipleValues,
WrongDataTypeInt(num::ParseIntError),
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))
}
}
pub trait DecodePostField<Config>: fmt::Debug {
fn from_field(config: Config, content: &str) -> Result<Self, PostFieldError>
where Self: Sized;
fn from_file<R>(config: Config, file: R, filename: Option<&str>, mime: &str)
-> Result<Self, PostFieldError>
where Self: Sized,
R: BufRead;
fn merge_multiple(self, _existing: Self) -> Result<Self, PostFieldError> where Self: Sized {
Err(PostFieldError::UnexpectedMultipleValues)
}
#[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())
}
}
#[derive(Clone)]
pub struct BufferedFile {
pub data: Vec<u8>,
pub mime: String,
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()),
})
}
}
#[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;
)*
if request.header("Content-Type").map(|ct| ct.starts_with("application/x-www-form-urlencoded")).unwrap_or(false) {
let body = {
let mut out = Vec::new();
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(mut multipart_entry) = multipart.next() {
$(
if multipart_entry.headers.name.as_ref() == stringify!($field) {
let config = ();
$(
let config = $config;
)*
if multipart_entry.is_text() {
let mut text = String::new();
multipart_entry.data.read_to_string(&mut text)?;
let decoded = match DecodePostField::from_field(config, &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,
}),
};
} else {
let name = multipart_entry.headers.filename.as_ref().map(|n| n.to_owned());
let name = name.as_ref().map(|n| &n[..]);
let mime = multipart_entry.headers.content_type
.map(|m| m.to_string())
.unwrap_or_else(String::new);
let decoded = match DecodePostField::from_file(config, multipart_entry.data, 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)
});
}
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 = {
let mut out = Vec::new();
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())
}
#[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]
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)
}
}
}