1use std::error;
11use std::fmt;
12use std::io::Error as IoError;
13use std::io::Read;
14use Request;
15
16#[derive(Debug)]
18pub enum PlainTextError {
19 BodyAlreadyExtracted,
21
22 WrongContentType,
24
25 IoError(IoError),
27
28 LimitExceeded,
30
31 NotUtf8,
33}
34
35impl From<IoError> for PlainTextError {
36 fn from(err: IoError) -> PlainTextError {
37 PlainTextError::IoError(err)
38 }
39}
40
41impl error::Error for PlainTextError {
42 #[inline]
43 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
44 match *self {
45 PlainTextError::IoError(ref e) => Some(e),
46 _ => None,
47 }
48 }
49}
50
51impl fmt::Display for PlainTextError {
52 #[inline]
53 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
54 let description = match *self {
55 PlainTextError::BodyAlreadyExtracted => "the body of the request was already extracted",
56 PlainTextError::WrongContentType => "the request didn't have a plain text content type",
57 PlainTextError::IoError(_) => {
58 "could not read the body from the request, or could not execute the CGI program"
59 }
60 PlainTextError::LimitExceeded => "the limit to the number of bytes has been exceeded",
61 PlainTextError::NotUtf8 => {
62 "the content-type encoding is not ASCII or UTF-8, or the body is not valid UTF-8"
63 }
64 };
65
66 write!(fmt, "{}", description)
67 }
68}
69
70#[inline]
92pub fn plain_text_body(request: &Request) -> Result<String, PlainTextError> {
93 plain_text_body_with_limit(request, 1024 * 1024)
94}
95
96pub fn plain_text_body_with_limit(
102 request: &Request,
103 limit: usize,
104) -> Result<String, PlainTextError> {
105 if let Some(header) = request.header("Content-Type") {
109 if !header.starts_with("text/plain") {
110 return Err(PlainTextError::WrongContentType);
111 }
112 } else {
113 return Err(PlainTextError::WrongContentType);
114 }
115
116 let body = match request.data() {
117 Some(b) => b,
118 None => return Err(PlainTextError::BodyAlreadyExtracted),
119 };
120
121 let mut out = Vec::new();
122 body.take(limit.saturating_add(1) as u64)
123 .read_to_end(&mut out)?;
124 if out.len() > limit {
125 return Err(PlainTextError::LimitExceeded);
126 }
127
128 let out = match String::from_utf8(out) {
129 Ok(o) => o,
130 Err(_) => return Err(PlainTextError::NotUtf8),
131 };
132
133 Ok(out)
134}
135
136#[cfg(test)]
137mod test {
138 use super::plain_text_body;
139 use super::plain_text_body_with_limit;
140 use super::PlainTextError;
141 use Request;
142
143 #[test]
144 fn ok() {
145 let request = Request::fake_http(
146 "GET",
147 "/",
148 vec![("Content-Type".to_owned(), "text/plain".to_owned())],
149 b"test".to_vec(),
150 );
151
152 match plain_text_body(&request) {
153 Ok(ref d) if d == "test" => (),
154 _ => panic!(),
155 }
156 }
157
158 #[test]
159 fn charset() {
160 let request = Request::fake_http(
161 "GET",
162 "/",
163 vec![(
164 "Content-Type".to_owned(),
165 "text/plain; charset=utf8".to_owned(),
166 )],
167 b"test".to_vec(),
168 );
169
170 match plain_text_body(&request) {
171 Ok(ref d) if d == "test" => (),
172 _ => panic!(),
173 }
174 }
175
176 #[test]
177 fn missing_content_type() {
178 let request = Request::fake_http("GET", "/", vec![], Vec::new());
179
180 match plain_text_body(&request) {
181 Err(PlainTextError::WrongContentType) => (),
182 _ => panic!(),
183 }
184 }
185
186 #[test]
187 fn wrong_content_type() {
188 let request = Request::fake_http(
189 "GET",
190 "/",
191 vec![("Content-Type".to_owned(), "text/html".to_owned())],
192 b"test".to_vec(),
193 );
194
195 match plain_text_body(&request) {
196 Err(PlainTextError::WrongContentType) => (),
197 _ => panic!(),
198 }
199 }
200
201 #[test]
202 fn body_twice() {
203 let request = Request::fake_http(
204 "GET",
205 "/",
206 vec![(
207 "Content-Type".to_owned(),
208 "text/plain; charset=utf8".to_owned(),
209 )],
210 b"test".to_vec(),
211 );
212
213 match plain_text_body(&request) {
214 Ok(ref d) if d == "test" => (),
215 _ => panic!(),
216 }
217
218 match plain_text_body(&request) {
219 Err(PlainTextError::BodyAlreadyExtracted) => (),
220 _ => panic!(),
221 }
222 }
223
224 #[test]
225 fn bytes_limit() {
226 let request = Request::fake_http(
227 "GET",
228 "/",
229 vec![("Content-Type".to_owned(), "text/plain".to_owned())],
230 b"test".to_vec(),
231 );
232
233 match plain_text_body_with_limit(&request, 2) {
234 Err(PlainTextError::LimitExceeded) => (),
235 _ => panic!(),
236 }
237 }
238
239 #[test]
240 fn exact_limit() {
241 let request = Request::fake_http(
242 "GET",
243 "/",
244 vec![("Content-Type".to_owned(), "text/plain".to_owned())],
245 b"test".to_vec(),
246 );
247
248 match plain_text_body_with_limit(&request, 4) {
249 Ok(ref d) if d == "test" => (),
250 _ => panic!(),
251 }
252 }
253
254 #[test]
255 fn non_utf8_body() {
256 let request = Request::fake_http(
257 "GET",
258 "/",
259 vec![(
260 "Content-Type".to_owned(),
261 "text/plain; charset=utf8".to_owned(),
262 )],
263 b"\xc3\x28".to_vec(),
264 );
265
266 match plain_text_body(&request) {
267 Err(PlainTextError::NotUtf8) => (),
268 _ => panic!(),
269 }
270 }
271
272 #[test]
273 #[ignore] fn non_utf8_encoding() {
275 let request = Request::fake_http(
276 "GET",
277 "/",
278 vec![(
279 "Content-Type".to_owned(),
280 "text/plain; charset=iso-8859-1".to_owned(),
281 )],
282 b"test".to_vec(),
283 );
284
285 match plain_text_body(&request) {
286 Err(PlainTextError::NotUtf8) => (),
287 _ => panic!(),
288 }
289 }
290}