ureq_proto/
error.rs

1use std::fmt;
2
3use http::{Method, StatusCode, Version};
4
5/// Error type for ureq-proto
6#[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, "more than one content-length header"),
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        state::{RecvResponse, Redirect, SendBody, SendRequest},
87        Call, RecvResponseResult, RedirectAuthHeaders, SendRequestResult,
88    };
89    use http::{HeaderValue, Method, Request, Version};
90
91    // Helper methods to reduce test verbosity
92
93    /// Creates a Call object from a request and proceeds to the initial state
94    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    /// Creates a Call object and proceeds to the RecvResponse state
102    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        // Write the request headers
107        call.write(&mut output).unwrap();
108
109        // Proceed to RecvResponse
110        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    /// Creates a Call object and proceeds to the SendBody state
119    fn setup_send_body_call(req: Request<()>) -> (Call<SendBody>, Vec<u8>, usize) {
120        let (mut call, mut output) = setup_call(req);
121
122        // Write the request headers
123        let n = call.write(&mut output).unwrap();
124
125        // Proceed to SendBody
126        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    /// Creates a Call object and proceeds to the Redirect state with the given response
135    fn setup_redirect_call(response: &[u8]) -> Result<(Call<Redirect>, Vec<u8>), Error> {
136        let (mut call, output) = setup_recv_response_call();
137
138        // Try to parse the response
139        let (_, _) = call.try_response(response, false)?;
140
141        // Proceed to Redirect state
142        let RecvResponseResult::Redirect(call) = call.proceed().unwrap() else {
143            panic!("Expected RecvResponseResult::Redirect");
144        };
145
146        Ok((call, output))
147    }
148
149    // BadHeader
150    #[test]
151    fn test_bad_header() {
152        // Create a request
153        let req = Request::get("http://example.com").body(()).unwrap();
154        let mut call = Call::new(req).unwrap();
155
156        // Try to set an invalid header using the Call API
157        let result = call.header("Invalid\0Header", "value");
158
159        // Verify that it returns a BadHeader error
160        assert!(result.is_err());
161        let err = result.unwrap_err();
162        assert!(matches!(err, Error::BadHeader(_)));
163    }
164
165    // UnsupportedVersion
166    #[test]
167    fn test_unsupported_version() {
168        // Create a request with HTTP/2.0 which is not supported by the Call API
169        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        // Try to write the request headers
178        let err = call.write(&mut output).unwrap_err();
179
180        assert!(matches!(err, Error::UnsupportedVersion));
181    }
182
183    // MethodVersionMismatch
184    #[test]
185    fn test_method_version_mismatch() {
186        // TRACE method is not valid for HTTP/1.0
187        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        // Try to write the request headers
198        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    // TooManyHostHeaders
208    #[test]
209    fn test_too_many_host_headers() {
210        // Create a request with two Host headers
211        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        // Try to write the request headers
221        let err = call.write(&mut output).unwrap_err();
222
223        // Verify that it returns a TooManyHostHeaders error
224        assert!(matches!(err, Error::TooManyHostHeaders));
225    }
226
227    // TooManyContentLengthHeaders
228    #[test]
229    fn test_too_many_content_length_headers() {
230        // Create a request with two Content-Length headers
231        let req = Request::builder()
232            .uri("http://example.com")
233            .header("Content-Length", "10")
234            .header("Content-Length", "20")
235            .body(())
236            .unwrap();
237
238        let (mut call, mut output) = setup_call(req);
239
240        // Try to write the request headers
241        let err = call.write(&mut output).unwrap_err();
242
243        // Verify that it returns a TooManyContentLengthHeaders error
244        assert!(matches!(err, Error::TooManyContentLengthHeaders));
245    }
246
247    // BadHostHeader
248    #[test]
249    fn test_bad_host_header() {
250        // Create a request with an invalid Host header value (non-UTF8 bytes)
251        let req = Request::builder()
252            .uri("http://example.com")
253            .header("Host", HeaderValue::from_bytes(&[0xFF, 0xFE]).unwrap())
254            .body(())
255            .unwrap();
256
257        let (mut call, mut output) = setup_call(req);
258
259        // Try to write the request headers
260        let err = call.write(&mut output).unwrap_err();
261
262        // Verify that it returns a BadHostHeader error
263        assert!(matches!(err, Error::BadHostHeader));
264    }
265
266    // BadAuthorizationHeader
267    #[test]
268    fn test_bad_authorization_header() {
269        // Create a request with an invalid Authorization header value (non-UTF8 bytes)
270        let req = Request::builder()
271            .uri("http://example.com")
272            .header(
273                "Authorization",
274                HeaderValue::from_bytes(&[0xFF, 0xFE]).unwrap(),
275            )
276            .body(())
277            .unwrap();
278
279        let (mut call, mut output) = setup_call(req);
280
281        // Try to write the request headers
282        let err = call.write(&mut output).unwrap_err();
283
284        // Verify that it returns a BadAuthorizationHeader error
285        assert!(matches!(err, Error::BadAuthorizationHeader));
286    }
287
288    // BadContentLengthHeader
289    #[test]
290    fn test_bad_content_length_header() {
291        // Create a request with an invalid Content-Length header value (not a number)
292        let req = Request::builder()
293            .uri("http://example.com")
294            .header("Content-Length", "not-a-number")
295            .body(())
296            .unwrap();
297
298        let (mut call, mut output) = setup_call(req);
299
300        // Try to write the request headers
301        let err = call.write(&mut output).unwrap_err();
302
303        // Verify that it returns a BadContentLengthHeader error
304        assert!(matches!(err, Error::BadContentLengthHeader));
305    }
306
307    // OutputOverflow
308    #[test]
309    fn test_output_overflow() {
310        // Create a request with a long header
311        let req = Request::builder()
312            .uri("http://example.com")
313            .header("x-long-header", "a".repeat(100))
314            .body(())
315            .unwrap();
316
317        let (mut call, _) = setup_call(req);
318
319        // Try to write to a very small output buffer
320        let mut tiny_output = vec![0; 10];
321        let err = call.write(&mut tiny_output).unwrap_err();
322
323        assert!(matches!(err, Error::OutputOverflow));
324    }
325
326    /// Tests a chunked encoding error with the given chunk data
327    fn test_chunk_error(chunk_data: &[u8]) -> Error {
328        let (mut call, mut output) = setup_recv_response_call();
329
330        const RES_PREFIX: &[u8] = b"HTTP/1.1 200 OK\r\n\
331                Transfer-Encoding: chunked\r\n\
332                \r\n";
333
334        call.try_response(RES_PREFIX, false).unwrap();
335
336        let RecvResponseResult::RecvBody(mut call) = call.proceed().unwrap() else {
337            panic!("Expected RecvResponseResult::RecvBody");
338        };
339
340        call.read(chunk_data, &mut output).unwrap_err()
341    }
342
343    // ChunkLenNotAscii
344    #[test]
345    fn test_chunk_len_not_ascii() {
346        let err = test_chunk_error(b"\xFF\r\ndata\r\n");
347        assert!(matches!(err, Error::ChunkLenNotAscii));
348    }
349
350    // ChunkLenNotANumber
351    #[test]
352    fn test_chunk_len_not_a_number() {
353        let err = test_chunk_error(b"xyz\r\ndata\r\n");
354        assert!(matches!(err, Error::ChunkLenNotANumber));
355    }
356
357    // ChunkExpectedCrLf
358    #[test]
359    fn test_chunk_expected_crlf() {
360        let err = test_chunk_error(b"5abcdefghijabcdefghijabcdefghij\r\n");
361        assert!(matches!(err, Error::ChunkExpectedCrLf));
362    }
363
364    // BodyContentAfterFinish
365    #[test]
366    fn test_body_content_after_finish() {
367        // Create a POST request
368        let req = Request::post("http://example.com").body(()).unwrap();
369
370        let (mut call, mut output, n) = setup_send_body_call(req);
371
372        // Write some data
373        let (_, n2) = call.write(b"data", &mut output[n..]).unwrap();
374
375        // Finish the body
376        let (_, n3) = call.write(&[], &mut output[n + n2..]).unwrap();
377
378        // Try to write more data after finishing
379        let err = call
380            .write(b"more data", &mut output[n + n2 + n3..])
381            .unwrap_err();
382        assert!(matches!(err, Error::BodyContentAfterFinish));
383    }
384
385    // BodyLargerThanContentLength
386    #[test]
387    fn test_body_larger_than_content_length() {
388        // Create a request with a content-length header
389        let req = Request::post("http://example.com")
390            .header("content-length", "5")
391            .body(())
392            .unwrap();
393
394        let (mut call, mut output, n) = setup_send_body_call(req);
395
396        // Try to write more data than specified in content-length
397        let err = call.write(b"too much data", &mut output[n..]).unwrap_err();
398        assert!(matches!(err, Error::BodyLargerThanContentLength));
399    }
400
401    // HttpParseFail
402    #[test]
403    fn test_http_parse_fail() {
404        let (mut call, _) = setup_recv_response_call();
405
406        // Invalid HTTP response (missing space after HTTP/1.1)
407        const RES: &[u8] = b"HTTP/1.1200 OK\r\n\r\n";
408
409        let err = call.try_response(RES, false).unwrap_err();
410
411        assert!(matches!(err, Error::HttpParseFail(_)));
412    }
413
414    // HttpParseTooManyHeaders
415    #[test]
416    fn test_http_parse_too_many_headers() {
417        let (mut call, _) = setup_recv_response_call();
418
419        // Create a response with many headers (more than the parser can handle)
420        let mut res = String::from("HTTP/1.1 200 OK\r\n");
421        for i in 0..1000 {
422            res.push_str(&format!("X-Header-{}: value\r\n", i));
423        }
424        res.push_str("\r\n");
425
426        let err = call.try_response(res.as_bytes(), false).unwrap_err();
427
428        assert!(matches!(err, Error::HttpParseTooManyHeaders));
429    }
430
431    // NoLocationHeader
432    #[test]
433    fn test_no_location_header() {
434        // Redirect response without a Location header
435        const RES: &[u8] = b"HTTP/1.1 302 Found\r\n\
436            \r\n";
437
438        let (mut call, _) = setup_redirect_call(RES).unwrap();
439
440        // Try to create a new Call, which should fail due to missing Location header
441        let err = call.as_new_call(RedirectAuthHeaders::Never).unwrap_err();
442
443        assert!(matches!(err, Error::NoLocationHeader));
444    }
445
446    // BadLocationHeader
447    #[test]
448    fn test_bad_location_header() {
449        // Redirect response with a malformed Location header
450        const RES: &[u8] = b"HTTP/1.1 302 Found\r\n\
451            Location: \xFF\r\n\
452            \r\n";
453
454        let (mut call, _) = setup_redirect_call(RES).unwrap();
455
456        // Try to create a new Call, which should fail due to malformed Location header
457        let err = call.as_new_call(RedirectAuthHeaders::Never).unwrap_err();
458
459        assert!(matches!(err, Error::BadLocationHeader(_)));
460    }
461
462    // HeadersWith100
463    #[test]
464    fn test_headers_with_100() {
465        let (mut call, _) = setup_recv_response_call();
466
467        // 100 Continue response with headers
468        const RES: &[u8] = b"HTTP/1.1 100 Continue\r\n\
469            Content-Type: text/plain\r\n\
470            \r\n";
471
472        let err = call.try_response(RES, false).unwrap_err();
473
474        assert!(matches!(err, Error::HeadersWith100));
475    }
476
477    // BodyIsChunked
478    #[test]
479    fn test_body_is_chunked() {
480        // Create a request with chunked transfer encoding
481        let req = Request::post("http://example.com")
482            .header("transfer-encoding", "chunked")
483            .body(())
484            .unwrap();
485
486        let (mut call, _, _) = setup_send_body_call(req);
487
488        // Try to use consume_direct_write which doesn't work with chunked encoding
489        let err = call.consume_direct_write(5).unwrap_err();
490        assert!(matches!(err, Error::BodyIsChunked));
491    }
492
493    // Test the From<httparse::Error> implementation
494    #[test]
495    fn test_from_httparse_error() {
496        let httparse_error = httparse::Error::HeaderName;
497        let error: Error = httparse_error.into();
498        assert!(matches!(error, Error::HttpParseFail(_)));
499        let Error::HttpParseFail(_) = error else {
500            panic!("Not Error::HttpParseFail");
501        };
502    }
503}