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