1 // Copyright (c) 2016 The Rouille developers
2 // Licensed under the Apache License, Version 2.0
3 // <LICENSE-APACHE or
4 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5 // license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
6 // at your option. All files in the project carrying such
7 // notice may not be copied, modified, or distributed except
8 // according to those terms.
9 
10 use std::error;
11 use std::fmt;
12 use std::io::Error as IoError;
13 use std::io::Read;
14 use Request;
15 
16 /// Error that can happen when parsing the request body as plain text.
17 #[derive(Debug)]
18 pub enum PlainTextError {
19     /// Can't parse the body of the request because it was already extracted.
20     BodyAlreadyExtracted,
21 
22     /// Wrong content type.
23     WrongContentType,
24 
25     /// Could not read the body from the request.
26     IoError(IoError),
27 
28     /// The limit to the number of bytes has been exceeded.
29     LimitExceeded,
30 
31     /// The content-type encoding is not ASCII or UTF-8, or the body is not valid UTF-8.
32     NotUtf8,
33 }
34 
35 impl From<IoError> for PlainTextError {
from(err: IoError) -> PlainTextError36     fn from(err: IoError) -> PlainTextError {
37         PlainTextError::IoError(err)
38     }
39 }
40 
41 impl error::Error for PlainTextError {
42     #[inline]
description(&self) -> &str43     fn description(&self) -> &str {
44         match *self {
45             PlainTextError::BodyAlreadyExtracted => {
46                 "the body of the request was already extracted"
47             },
48             PlainTextError::WrongContentType => {
49                 "the request didn't have a plain text content type"
50             },
51             PlainTextError::IoError(_) => {
52                 "could not read the body from the request, or could not execute the CGI program"
53             },
54             PlainTextError::LimitExceeded => {
55                 "the limit to the number of bytes has been exceeded"
56             },
57             PlainTextError::NotUtf8 => {
58                 "the content-type encoding is not ASCII or UTF-8, or the body is not valid UTF-8"
59             },
60         }
61     }
62 
63     #[inline]
cause(&self) -> Option<&error::Error>64     fn cause(&self) -> Option<&error::Error> {
65         match *self {
66             PlainTextError::IoError(ref e) => Some(e),
67             _ => None
68         }
69     }
70 }
71 
72 impl fmt::Display for PlainTextError {
73     #[inline]
fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error>74     fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
75         write!(fmt, "{}", error::Error::description(self))
76     }
77 }
78 
79 /// Read plain text data from the body of a request.
80 ///
81 /// Returns an error if the content-type of the request is not text/plain. Only the UTF-8 encoding
82 /// is supported. You will get an error if the client passed non-UTF8 data.
83 ///
84 /// If the body of the request exceeds 1MB of data, an error is returned to prevent a malicious
85 /// client from crashing the server. Use the `plain_text_body_with_limit` function to customize
86 /// the limit.
87 ///
88 /// # Example
89 ///
90 /// ```
91 /// # #[macro_use] extern crate rouille;
92 /// # use rouille::{Request, Response};
93 /// # fn main() {}
94 /// fn route_handler(request: &Request) -> Response {
95 ///     let text = try_or_400!(rouille::input::plain_text_body(request));
96 ///     Response::text(format!("you sent: {}", text))
97 /// }
98 /// ```
99 ///
100 #[inline]
plain_text_body(request: &Request) -> Result<String, PlainTextError>101 pub fn plain_text_body(request: &Request) -> Result<String, PlainTextError> {
102     plain_text_body_with_limit(request, 1024 * 1024)
103 }
104 
105 /// Reads plain text data from the body of a request.
106 ///
107 /// This does the same as `plain_text_body`, but with a customizable limit in bytes to how much
108 /// data will be read from the request. If the limit is exceeded, a `LimitExceeded` error is
109 /// returned.
plain_text_body_with_limit(request: &Request, limit: usize) -> Result<String, PlainTextError>110 pub fn plain_text_body_with_limit(request: &Request, limit: usize)
111                                   -> Result<String, PlainTextError>
112 {
113     // TODO: handle encoding ; return NotUtf8 if a non-utf8 charset is sent
114     // if no encoding is specified by the client, the default is `US-ASCII` which is compatible with UTF8
115 
116     if let Some(header) = request.header("Content-Type") {
117         if !header.starts_with("text/plain") {
118             return Err(PlainTextError::WrongContentType);
119         }
120     } else {
121         return Err(PlainTextError::WrongContentType);
122     }
123 
124     let body = match request.data() {
125         Some(b) => b,
126         None => return Err(PlainTextError::BodyAlreadyExtracted),
127     };
128 
129     let mut out = Vec::new();
130     try!(body.take(limit.saturating_add(1) as u64).read_to_end(&mut out));
131     if out.len() > limit {
132         return Err(PlainTextError::LimitExceeded);
133     }
134 
135     let out = match String::from_utf8(out) {
136         Ok(o) => o,
137         Err(_) => return Err(PlainTextError::NotUtf8),
138     };
139 
140     Ok(out)
141 }
142 
143 #[cfg(test)]
144 mod test {
145     use Request;
146     use super::plain_text_body;
147     use super::plain_text_body_with_limit;
148     use super::PlainTextError;
149 
150     #[test]
ok()151     fn ok() {
152         let request = Request::fake_http("GET", "/", vec![
153             ("Content-Type".to_owned(), "text/plain".to_owned())
154         ], b"test".to_vec());
155 
156         match plain_text_body(&request) {
157             Ok(ref d) if d == "test" => (),
158             _ => panic!()
159         }
160     }
161 
162     #[test]
charset()163     fn charset() {
164         let request = Request::fake_http("GET", "/", vec![
165             ("Content-Type".to_owned(), "text/plain; charset=utf8".to_owned())
166         ], b"test".to_vec());
167 
168         match plain_text_body(&request) {
169             Ok(ref d) if d == "test" => (),
170             _ => panic!()
171         }
172     }
173 
174     #[test]
missing_content_type()175     fn missing_content_type() {
176         let request = Request::fake_http("GET", "/", vec![], Vec::new());
177 
178         match plain_text_body(&request) {
179             Err(PlainTextError::WrongContentType) => (),
180             _ => panic!()
181         }
182     }
183 
184     #[test]
wrong_content_type()185     fn wrong_content_type() {
186         let request = Request::fake_http("GET", "/", vec![
187             ("Content-Type".to_owned(), "text/html".to_owned())
188         ], b"test".to_vec());
189 
190         match plain_text_body(&request) {
191             Err(PlainTextError::WrongContentType) => (),
192             _ => panic!()
193         }
194     }
195 
196     #[test]
body_twice()197     fn body_twice() {
198         let request = Request::fake_http("GET", "/", vec![
199             ("Content-Type".to_owned(), "text/plain; charset=utf8".to_owned())
200         ], b"test".to_vec());
201 
202         match plain_text_body(&request) {
203             Ok(ref d) if d == "test" => (),
204             _ => panic!()
205         }
206 
207         match plain_text_body(&request) {
208             Err(PlainTextError::BodyAlreadyExtracted) => (),
209             _ => panic!()
210         }
211     }
212 
213     #[test]
bytes_limit()214     fn bytes_limit() {
215         let request = Request::fake_http("GET", "/", vec![
216             ("Content-Type".to_owned(), "text/plain".to_owned())
217         ], b"test".to_vec());
218 
219         match plain_text_body_with_limit(&request, 2) {
220             Err(PlainTextError::LimitExceeded) => (),
221             _ => panic!()
222         }
223     }
224 
225     #[test]
exact_limit()226     fn exact_limit() {
227         let request = Request::fake_http("GET", "/", vec![
228             ("Content-Type".to_owned(), "text/plain".to_owned())
229         ], b"test".to_vec());
230 
231         match plain_text_body_with_limit(&request, 4) {
232             Ok(ref d) if d == "test" => (),
233             _ => panic!()
234         }
235     }
236 
237     #[test]
non_utf8_body()238     fn non_utf8_body() {
239         let request = Request::fake_http("GET", "/", vec![
240             ("Content-Type".to_owned(), "text/plain; charset=utf8".to_owned())
241         ], b"\xc3\x28".to_vec());
242 
243         match plain_text_body(&request) {
244             Err(PlainTextError::NotUtf8) => (),
245             _ => panic!()
246         }
247     }
248 
249     #[test]
250     #[ignore]       // TODO: not implemented
non_utf8_encoding()251     fn non_utf8_encoding() {
252         let request = Request::fake_http("GET", "/", vec![
253             ("Content-Type".to_owned(), "text/plain; charset=iso-8859-1".to_owned())
254         ], b"test".to_vec());
255 
256         match plain_text_body(&request) {
257             Err(PlainTextError::NotUtf8) => (),
258             _ => panic!()
259         }
260     }
261 }
262