use std::error;
use std::fmt;
use std::io::Error as IoError;
use std::io::Read;
use Request;
#[derive(Debug)]
pub enum PlainTextError {
BodyAlreadyExtracted,
WrongContentType,
IoError(IoError),
LimitExceeded,
NotUtf8,
}
impl From<IoError> for PlainTextError {
fn from(err: IoError) -> PlainTextError {
PlainTextError::IoError(err)
}
}
impl error::Error for PlainTextError {
#[inline]
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
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> {
let description = 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"
}
};
write!(fmt, "{}", description)
}
}
#[inline]
pub fn plain_text_body(request: &Request) -> Result<String, PlainTextError> {
plain_text_body_with_limit(request, 1024 * 1024)
}
pub fn plain_text_body_with_limit(
request: &Request,
limit: usize,
) -> Result<String, PlainTextError> {
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 super::plain_text_body;
use super::plain_text_body_with_limit;
use super::PlainTextError;
use Request;
#[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] 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!(),
}
}
}