| // 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. | |
| use std::error; | |
| use std::fmt; | |
| use std::io::Error as IoError; | |
| use std::io::Read; | |
| use Request; | |
| /// Error that can happen when parsing the request body as plain text. | |
| #[derive(Debug)] | |
| pub enum PlainTextError { | |
| /// Can't parse the body of the request because it was already extracted. | |
| BodyAlreadyExtracted, | |
| /// Wrong content type. | |
| WrongContentType, | |
| /// Could not read the body from the request. | |
| IoError(IoError), | |
| /// The limit to the number of bytes has been exceeded. | |
| LimitExceeded, | |
| /// The content-type encoding is not ASCII or UTF-8, or the body is not valid UTF-8. | |
| NotUtf8, | |
| } | |
| impl From<IoError> for PlainTextError { | |
| fn from(err: IoError) -> PlainTextError { | |
| PlainTextError::IoError(err) | |
| } | |
| } | |
| impl error::Error for PlainTextError { | |
| #[inline] | |
| fn description(&self) -> &str { | |
| match *self { | |
| PlainTextError::BodyAlreadyExtracted => { | |
| "the body of the request was already extracted" | |
| }, | |
| PlainTextError::WrongContentType => { | |
| "the request didn't have a plain text content type" | |
| }, | |
| PlainTextError::IoError(_) => { | |
| "could not read the body from the request, or could not execute the CGI program" | |
| }, | |
| PlainTextError::LimitExceeded => { | |
| "the limit to the number of bytes has been exceeded" | |
| }, | |
| PlainTextError::NotUtf8 => { | |
| "the content-type encoding is not ASCII or UTF-8, or the body is not valid UTF-8" | |
| }, | |
| } | |
| } | |
| #[inline] | |
| fn cause(&self) -> Option<&error::Error> { | |
| match *self { | |
| PlainTextError::IoError(ref e) => Some(e), | |
| _ => None | |
| } | |
| } | |
| } | |
| impl fmt::Display for PlainTextError { | |
| #[inline] | |
| fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { | |
| write!(fmt, "{}", error::Error::description(self)) | |
| } | |
| } | |
| /// Read plain text data from the body of a request. | |
| /// | |
| /// Returns an error if the content-type of the request is not text/plain. Only the UTF-8 encoding | |
| /// is supported. You will get an error if the client passed non-UTF8 data. | |
| /// | |
| /// If the body of the request exceeds 1MB of data, an error is returned to prevent a malicious | |
| /// client from crashing the server. Use the `plain_text_body_with_limit` function to customize | |
| /// the limit. | |
| /// | |
| /// # Example | |
| /// | |
| /// ``` | |
| /// # #[macro_use] extern crate rouille; | |
| /// # use rouille::{Request, Response}; | |
| /// # fn main() {} | |
| /// fn route_handler(request: &Request) -> Response { | |
| /// let text = try_or_400!(rouille::input::plain_text_body(request)); | |
| /// Response::text(format!("you sent: {}", text)) | |
| /// } | |
| /// ``` | |
| /// | |
| #[inline] | |
| pub fn plain_text_body(request: &Request) -> Result<String, PlainTextError> { | |
| plain_text_body_with_limit(request, 1024 * 1024) | |
| } | |
| /// Reads plain text data from the body of a request. | |
| /// | |
| /// This does the same as `plain_text_body`, but with a customizable limit in bytes to how much | |
| /// data will be read from the request. If the limit is exceeded, a `LimitExceeded` error is | |
| /// returned. | |
| pub fn plain_text_body_with_limit(request: &Request, limit: usize) | |
| -> Result<String, PlainTextError> | |
| { | |
| // TODO: handle encoding ; return NotUtf8 if a non-utf8 charset is sent | |
| // if no encoding is specified by the client, the default is `US-ASCII` which is compatible with UTF8 | |
| if let Some(header) = request.header("Content-Type") { | |
| if !header.starts_with("text/plain") { | |
| return Err(PlainTextError::WrongContentType); | |
| } | |
| } else { | |
| return Err(PlainTextError::WrongContentType); | |
| } | |
| let body = match request.data() { | |
| Some(b) => b, | |
| None => return Err(PlainTextError::BodyAlreadyExtracted), | |
| }; | |
| let mut out = Vec::new(); | |
| try!(body.take(limit.saturating_add(1) as u64).read_to_end(&mut out)); | |
| if out.len() > limit { | |
| return Err(PlainTextError::LimitExceeded); | |
| } | |
| let out = match String::from_utf8(out) { | |
| Ok(o) => o, | |
| Err(_) => return Err(PlainTextError::NotUtf8), | |
| }; | |
| Ok(out) | |
| } | |
| #[cfg(test)] | |
| mod test { | |
| use Request; | |
| use super::plain_text_body; | |
| use super::plain_text_body_with_limit; | |
| use super::PlainTextError; | |
| #[test] | |
| fn ok() { | |
| let request = Request::fake_http("GET", "/", vec![ | |
| ("Content-Type".to_owned(), "text/plain".to_owned()) | |
| ], b"test".to_vec()); | |
| match plain_text_body(&request) { | |
| Ok(ref d) if d == "test" => (), | |
| _ => panic!() | |
| } | |
| } | |
| #[test] | |
| fn charset() { | |
| let request = Request::fake_http("GET", "/", vec![ | |
| ("Content-Type".to_owned(), "text/plain; charset=utf8".to_owned()) | |
| ], b"test".to_vec()); | |
| match plain_text_body(&request) { | |
| Ok(ref d) if d == "test" => (), | |
| _ => panic!() | |
| } | |
| } | |
| #[test] | |
| fn missing_content_type() { | |
| let request = Request::fake_http("GET", "/", vec![], Vec::new()); | |
| match plain_text_body(&request) { | |
| Err(PlainTextError::WrongContentType) => (), | |
| _ => panic!() | |
| } | |
| } | |
| #[test] | |
| fn wrong_content_type() { | |
| let request = Request::fake_http("GET", "/", vec![ | |
| ("Content-Type".to_owned(), "text/html".to_owned()) | |
| ], b"test".to_vec()); | |
| match plain_text_body(&request) { | |
| Err(PlainTextError::WrongContentType) => (), | |
| _ => panic!() | |
| } | |
| } | |
| #[test] | |
| fn body_twice() { | |
| let request = Request::fake_http("GET", "/", vec![ | |
| ("Content-Type".to_owned(), "text/plain; charset=utf8".to_owned()) | |
| ], b"test".to_vec()); | |
| match plain_text_body(&request) { | |
| Ok(ref d) if d == "test" => (), | |
| _ => panic!() | |
| } | |
| match plain_text_body(&request) { | |
| Err(PlainTextError::BodyAlreadyExtracted) => (), | |
| _ => panic!() | |
| } | |
| } | |
| #[test] | |
| fn bytes_limit() { | |
| let request = Request::fake_http("GET", "/", vec![ | |
| ("Content-Type".to_owned(), "text/plain".to_owned()) | |
| ], b"test".to_vec()); | |
| match plain_text_body_with_limit(&request, 2) { | |
| Err(PlainTextError::LimitExceeded) => (), | |
| _ => panic!() | |
| } | |
| } | |
| #[test] | |
| fn exact_limit() { | |
| let request = Request::fake_http("GET", "/", vec![ | |
| ("Content-Type".to_owned(), "text/plain".to_owned()) | |
| ], b"test".to_vec()); | |
| match plain_text_body_with_limit(&request, 4) { | |
| Ok(ref d) if d == "test" => (), | |
| _ => panic!() | |
| } | |
| } | |
| #[test] | |
| fn non_utf8_body() { | |
| let request = Request::fake_http("GET", "/", vec![ | |
| ("Content-Type".to_owned(), "text/plain; charset=utf8".to_owned()) | |
| ], b"\xc3\x28".to_vec()); | |
| match plain_text_body(&request) { | |
| Err(PlainTextError::NotUtf8) => (), | |
| _ => panic!() | |
| } | |
| } | |
| #[test] | |
| #[ignore] // TODO: not implemented | |
| fn non_utf8_encoding() { | |
| let request = Request::fake_http("GET", "/", vec![ | |
| ("Content-Type".to_owned(), "text/plain; charset=iso-8859-1".to_owned()) | |
| ], b"test".to_vec()); | |
| match plain_text_body(&request) { | |
| Err(PlainTextError::NotUtf8) => (), | |
| _ => panic!() | |
| } | |
| } | |
| } |