1use std::fmt;
2
3use http::{Method, StatusCode, Version};
4
5#[derive(Debug, PartialEq, Eq)]
7#[allow(missing_docs)]
8#[non_exhaustive]
9pub enum Error {
10 BadHeader(String),
11 UnsupportedVersion,
12 MethodVersionMismatch(Method, Version),
13 TooManyHostHeaders,
14 TooManyContentLengthHeaders,
15 BadHostHeader,
16 BadAuthorizationHeader,
17 BadContentLengthHeader,
18 OutputOverflow,
19 ChunkLenNotAscii,
20 ChunkLenNotANumber,
21 ChunkExpectedCrLf,
22 BodyContentAfterFinish,
23 BodyLargerThanContentLength,
24 BodyNotAllowed,
25 HttpParseFail(String),
26 HttpParseTooManyHeaders,
27 NoLocationHeader,
28 BadLocationHeader(String),
29 HeadersWith100,
30 BodyIsChunked,
31 BadReject100Status(StatusCode),
32}
33
34impl From<httparse::Error> for Error {
35 fn from(value: httparse::Error) -> Self {
36 Error::HttpParseFail(value.to_string())
37 }
38}
39
40impl std::error::Error for Error {}
41
42impl fmt::Display for Error {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 match self {
45 Error::BadHeader(v) => write!(f, "bad header: {}", v),
46 Error::UnsupportedVersion => write!(f, "unsupported http version"),
47 Error::MethodVersionMismatch(m, v) => {
48 write!(f, "{} not valid for HTTP version {:?}", m, v)
49 }
50 Error::TooManyHostHeaders => write!(f, "more than one host header"),
51 Error::TooManyContentLengthHeaders => write!(f, "conflicting content-length headers"),
52 Error::BadHostHeader => write!(f, "host header is not a string"),
53 Error::BadAuthorizationHeader => write!(f, "authorization header is not a string"),
54 Error::BadContentLengthHeader => write!(f, "content-length header not a number"),
55 Error::OutputOverflow => write!(f, "output too small to write output"),
56 Error::ChunkLenNotAscii => write!(f, "chunk length is not ascii"),
57 Error::ChunkLenNotANumber => write!(f, "chunk length cannot be read as a number"),
58 Error::ChunkExpectedCrLf => write!(f, "chunk expected crlf as next character"),
59 Error::BodyContentAfterFinish => {
60 write!(f, "attempt to stream body after sending finish (&[])")
61 }
62 Error::BodyLargerThanContentLength => {
63 write!(f, "attempt to write larger body than content-length")
64 }
65 Error::BodyNotAllowed => {
66 write!(f, "body not allowed by method")
67 }
68 Error::HttpParseFail(v) => write!(f, "http parse fail: {}", v),
69 Error::HttpParseTooManyHeaders => write!(f, "http parse resulted in too many headers"),
70 Error::NoLocationHeader => write!(f, "missing a location header"),
71 Error::BadLocationHeader(v) => write!(f, "location header is malformed: {}", v),
72 Error::HeadersWith100 => write!(f, "received headers with 100-continue response"),
73 Error::BodyIsChunked => write!(f, "body is chunked"),
74 Error::BadReject100Status(v) => {
75 write!(f, "expect-100 must be rejected with 4xx or 5xx: {}", v)
76 }
77 }
78 }
79}
80
81#[cfg(test)]
82#[cfg(feature = "client")]
83mod tests_client {
84 use super::*;
85 use crate::client::{
86 Call, RecvResponseResult, RedirectAuthHeaders, SendRequestResult,
87 state::{RecvResponse, Redirect, SendBody, SendRequest},
88 };
89 use http::{HeaderValue, Method, Request, Version};
90
91 fn setup_call(req: Request<()>) -> (Call<SendRequest>, Vec<u8>) {
95 let call = Call::new(req).unwrap();
96 let call = call.proceed();
97 let output = vec![0; 1024];
98 (call, output)
99 }
100
101 fn setup_recv_response_call() -> (Call<RecvResponse>, Vec<u8>) {
103 let req = Request::get("http://example.com").body(()).unwrap();
104 let (mut call, mut output) = setup_call(req);
105
106 call.write(&mut output).unwrap();
108
109 let call = call.proceed().unwrap().unwrap();
111 let SendRequestResult::RecvResponse(call) = call else {
112 panic!("Expected SendRequestResult::RecvResponse");
113 };
114
115 (call, output)
116 }
117
118 fn setup_send_body_call(req: Request<()>) -> (Call<SendBody>, Vec<u8>, usize) {
120 let (mut call, mut output) = setup_call(req);
121
122 let n = call.write(&mut output).unwrap();
124
125 let call = call.proceed().unwrap().unwrap();
127 let SendRequestResult::SendBody(call) = call else {
128 panic!("Expected SendBody");
129 };
130
131 (call, output, n)
132 }
133
134 fn setup_redirect_call(response: &[u8]) -> Result<(Call<Redirect>, Vec<u8>), Error> {
136 let (mut call, output) = setup_recv_response_call();
137
138 let (_, _) = call.try_response(response, false)?;
140
141 let RecvResponseResult::Redirect(call) = call.proceed().unwrap() else {
143 panic!("Expected RecvResponseResult::Redirect");
144 };
145
146 Ok((call, output))
147 }
148
149 #[test]
151 fn test_bad_header() {
152 let req = Request::get("http://example.com").body(()).unwrap();
154 let mut call = Call::new(req).unwrap();
155
156 let result = call.header("Invalid\0Header", "value");
158
159 assert!(result.is_err());
161 let err = result.unwrap_err();
162 assert!(matches!(err, Error::BadHeader(_)));
163 }
164
165 #[test]
167 fn test_unsupported_version() {
168 let req = Request::builder()
170 .uri("http://example.com")
171 .version(Version::HTTP_2)
172 .body(())
173 .unwrap();
174
175 let (mut call, mut output) = setup_call(req);
176
177 let err = call.write(&mut output).unwrap_err();
179
180 assert!(matches!(err, Error::UnsupportedVersion));
181 }
182
183 #[test]
185 fn test_method_version_mismatch() {
186 let m = Method::from_bytes(b"TRACE").unwrap();
188 let req = Request::builder()
189 .method(m.clone())
190 .uri("http://example.com")
191 .version(Version::HTTP_10)
192 .body(())
193 .unwrap();
194
195 let (mut call, mut output) = setup_call(req);
196
197 let err = call.write(&mut output).unwrap_err();
199
200 assert!(matches!(err, Error::MethodVersionMismatch(_, _)));
201 if let Error::MethodVersionMismatch(method, version) = err {
202 assert_eq!(method, m);
203 assert_eq!(version, Version::HTTP_10);
204 }
205 }
206
207 #[test]
209 fn test_too_many_host_headers() {
210 let req = Request::builder()
212 .uri("http://example.com")
213 .header("Host", "example.com")
214 .header("Host", "another-example.com")
215 .body(())
216 .unwrap();
217
218 let (mut call, mut output) = setup_call(req);
219
220 let err = call.write(&mut output).unwrap_err();
222
223 assert!(matches!(err, Error::TooManyHostHeaders));
225 }
226
227 #[test]
229 fn test_signed_content_length_header() {
230 let req = Request::builder()
233 .uri("http://example.com")
234 .header("Content-Length", "+10")
235 .body(())
236 .unwrap();
237
238 let (mut call, mut output) = setup_call(req);
239
240 let err = call.write(&mut output).unwrap_err();
242
243 assert!(matches!(err, Error::BadContentLengthHeader));
244 }
245
246 #[test]
248 fn test_list_content_length_header() {
249 let req = Request::builder()
253 .uri("http://example.com")
254 .header("Content-Length", "42, 42")
255 .body(())
256 .unwrap();
257
258 let (mut call, mut output) = setup_call(req);
259
260 let err = call.write(&mut output).unwrap_err();
262
263 assert!(matches!(
264 err,
265 Error::BadContentLengthHeader | Error::TooManyContentLengthHeaders
266 ));
267 }
268
269 #[test]
271 fn test_too_many_content_length_headers() {
272 let req = Request::builder()
274 .uri("http://example.com")
275 .header("Content-Length", "10")
276 .header("Content-Length", "20")
277 .body(())
278 .unwrap();
279
280 let (mut call, mut output) = setup_call(req);
281
282 let err = call.write(&mut output).unwrap_err();
284
285 assert!(matches!(err, Error::TooManyContentLengthHeaders));
287 }
288
289 #[test]
291 fn test_bad_host_header() {
292 let req = Request::builder()
294 .uri("http://example.com")
295 .header("Host", HeaderValue::from_bytes(&[0xFF, 0xFE]).unwrap())
296 .body(())
297 .unwrap();
298
299 let (mut call, mut output) = setup_call(req);
300
301 let err = call.write(&mut output).unwrap_err();
303
304 assert!(matches!(err, Error::BadHostHeader));
306 }
307
308 #[test]
310 fn test_bad_authorization_header() {
311 let req = Request::builder()
313 .uri("http://example.com")
314 .header(
315 "Authorization",
316 HeaderValue::from_bytes(&[0xFF, 0xFE]).unwrap(),
317 )
318 .body(())
319 .unwrap();
320
321 let (mut call, mut output) = setup_call(req);
322
323 let err = call.write(&mut output).unwrap_err();
325
326 assert!(matches!(err, Error::BadAuthorizationHeader));
328 }
329
330 #[test]
332 fn test_bad_content_length_header() {
333 let req = Request::builder()
335 .uri("http://example.com")
336 .header("Content-Length", "not-a-number")
337 .body(())
338 .unwrap();
339
340 let (mut call, mut output) = setup_call(req);
341
342 let err = call.write(&mut output).unwrap_err();
344
345 assert!(matches!(err, Error::BadContentLengthHeader));
347 }
348
349 #[test]
351 fn test_output_overflow() {
352 let req = Request::builder()
354 .uri("http://example.com")
355 .header("x-long-header", "a".repeat(100))
356 .body(())
357 .unwrap();
358
359 let (mut call, _) = setup_call(req);
360
361 let mut tiny_output = vec![0; 10];
363 let err = call.write(&mut tiny_output).unwrap_err();
364
365 assert!(matches!(err, Error::OutputOverflow));
366 }
367
368 fn test_chunk_error(chunk_data: &[u8]) -> Error {
370 let (mut call, mut output) = setup_recv_response_call();
371
372 const RES_PREFIX: &[u8] = b"HTTP/1.1 200 OK\r\n\
373 Transfer-Encoding: chunked\r\n\
374 \r\n";
375
376 call.try_response(RES_PREFIX, false).unwrap();
377
378 let RecvResponseResult::RecvBody(mut call) = call.proceed().unwrap() else {
379 panic!("Expected RecvResponseResult::RecvBody");
380 };
381
382 call.read(chunk_data, &mut output).unwrap_err()
383 }
384
385 #[test]
387 fn test_chunk_len_not_ascii() {
388 let err = test_chunk_error(b"\xFF\r\ndata\r\n");
389 assert!(matches!(err, Error::ChunkLenNotAscii));
390 }
391
392 #[test]
394 fn test_chunk_len_not_a_number() {
395 let err = test_chunk_error(b"xyz\r\ndata\r\n");
396 assert!(matches!(err, Error::ChunkLenNotANumber));
397 }
398
399 #[test]
401 fn test_chunk_expected_crlf() {
402 let err = test_chunk_error(b"5abcdefghijabcdefghijabcdefghij\r\n");
403 assert!(matches!(err, Error::ChunkExpectedCrLf));
404 }
405
406 #[test]
408 fn test_body_content_after_finish() {
409 let req = Request::post("http://example.com").body(()).unwrap();
411
412 let (mut call, mut output, n) = setup_send_body_call(req);
413
414 let (_, n2) = call.write(b"data", &mut output[n..]).unwrap();
416
417 let (_, n3) = call.write(&[], &mut output[n + n2..]).unwrap();
419
420 let err = call
422 .write(b"more data", &mut output[n + n2 + n3..])
423 .unwrap_err();
424 assert!(matches!(err, Error::BodyContentAfterFinish));
425 }
426
427 #[test]
429 fn test_body_larger_than_content_length() {
430 let req = Request::post("http://example.com")
432 .header("content-length", "5")
433 .body(())
434 .unwrap();
435
436 let (mut call, mut output, n) = setup_send_body_call(req);
437
438 let err = call.write(b"too much data", &mut output[n..]).unwrap_err();
440 assert!(matches!(err, Error::BodyLargerThanContentLength));
441 }
442
443 #[test]
445 fn test_http_parse_fail() {
446 let (mut call, _) = setup_recv_response_call();
447
448 const RES: &[u8] = b"HTTP/1.1200 OK\r\n\r\n";
450
451 let err = call.try_response(RES, false).unwrap_err();
452
453 assert!(matches!(err, Error::HttpParseFail(_)));
454 }
455
456 #[test]
458 fn test_http_parse_too_many_headers() {
459 let (mut call, _) = setup_recv_response_call();
460
461 let mut res = String::from("HTTP/1.1 200 OK\r\n");
463 for i in 0..1000 {
464 res.push_str(&format!("X-Header-{}: value\r\n", i));
465 }
466 res.push_str("\r\n");
467
468 let err = call.try_response(res.as_bytes(), false).unwrap_err();
469
470 assert!(matches!(err, Error::HttpParseTooManyHeaders));
471 }
472
473 #[test]
475 fn test_no_location_header() {
476 const RES: &[u8] = b"HTTP/1.1 302 Found\r\n\
478 \r\n";
479
480 let (mut call, _) = setup_redirect_call(RES).unwrap();
481
482 let err = call.as_new_call(RedirectAuthHeaders::Never).unwrap_err();
484
485 assert!(matches!(err, Error::NoLocationHeader));
486 }
487
488 #[test]
490 fn test_bad_location_header() {
491 const RES: &[u8] = b"HTTP/1.1 302 Found\r\n\
493 Location: \xFF\r\n\
494 \r\n";
495
496 let (mut call, _) = setup_redirect_call(RES).unwrap();
497
498 let err = call.as_new_call(RedirectAuthHeaders::Never).unwrap_err();
500
501 assert!(matches!(err, Error::BadLocationHeader(_)));
502 }
503
504 #[test]
506 fn test_headers_with_100() {
507 let (mut call, _) = setup_recv_response_call();
508
509 const RES: &[u8] = b"HTTP/1.1 100 Continue\r\n\
511 Content-Type: text/plain\r\n\
512 \r\n";
513
514 let err = call.try_response(RES, false).unwrap_err();
515
516 assert!(matches!(err, Error::HeadersWith100));
517 }
518
519 #[test]
521 fn test_body_is_chunked() {
522 let req = Request::post("http://example.com")
524 .header("transfer-encoding", "chunked")
525 .body(())
526 .unwrap();
527
528 let (mut call, _, _) = setup_send_body_call(req);
529
530 let err = call.consume_direct_write(5).unwrap_err();
532 assert!(matches!(err, Error::BodyIsChunked));
533 }
534
535 #[test]
537 fn test_from_httparse_error() {
538 let httparse_error = httparse::Error::HeaderName;
539 let error: Error = httparse_error.into();
540 assert!(matches!(error, Error::HttpParseFail(_)));
541 let Error::HttpParseFail(_) = error else {
542 panic!("Not Error::HttpParseFail");
543 };
544 }
545}