/rust/registry/src/index.crates.io-1949cf8c6b5b557f/ureq-proto-0.6.1/src/client/mod.rs
Line | Count | Source |
1 | | //! HTTP/1.1 client protocol |
2 | | //! |
3 | | //! Sans-IO protocol impl, which means "writing" and "reading" are made via buffers |
4 | | //! rather than the Write/Read std traits. |
5 | | //! |
6 | | //! The [`Call`] object attempts to encode correct HTTP/1.1 handling using |
7 | | //! state variables, for example `Call<'a, SendRequest>` to represent the |
8 | | //! lifecycle stage where we are to send the request. |
9 | | //! |
10 | | //! The states are: |
11 | | //! |
12 | | //! * **Prepare** - Preparing a request means 1) adding headers such as |
13 | | //! cookies. 2) acquiring the connection from a pool or opening a new |
14 | | //! socket (potentially wrappping in TLS) |
15 | | //! * **SendRequest** - Send the first row, which is the method, path |
16 | | //! and version as well as the request headers |
17 | | //! * **SendBody** - Send the request body |
18 | | //! * **Await100** - If there is an `Expect: 100-continue` header, the |
19 | | //! client should pause before sending the body |
20 | | //! * **RecvResponse** - Receive the response, meaning the status and |
21 | | //! version and the response headers |
22 | | //! * **RecvBody** - Receive the response body |
23 | | //! * **Redirect** - Handle redirects, potentially spawning new requests |
24 | | //! * **Cleanup** - Return the connection to the pool or close it |
25 | | //! |
26 | | //! |
27 | | //! ```text |
28 | | //! ┌──────────────────┐ |
29 | | //! ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ▶│ Prepare │ |
30 | | //! └──────────────────┘ |
31 | | //! │ │ |
32 | | //! ▼ |
33 | | //! │ ┌──────────────────┐ |
34 | | //! ┌──│ SendRequest │──────────────┐ |
35 | | //! │ │ └──────────────────┘ │ |
36 | | //! │ │ │ |
37 | | //! │ │ ▼ ▼ |
38 | | //! │ ┌──────────────────┐ ┌──────────────────┐ |
39 | | //! │ │ │ SendBody │◀───│ Await100 │ |
40 | | //! │ └──────────────────┘ └──────────────────┘ |
41 | | //! │ │ │ │ |
42 | | //! │ ▼ │ |
43 | | //! │ └─▶┌──────────────────┐◀─────────────┘ |
44 | | //! ┌─────────────│ RecvResponse │──┐ |
45 | | //! │ │ └──────────────────┘ │ |
46 | | //! │ │ │ |
47 | | //! │ ▼ ▼ │ |
48 | | //! ┌──────────────────┐ ┌──────────────────┐ │ |
49 | | //! └ ─│ Redirect │◀───│ RecvBody │ │ |
50 | | //! └──────────────────┘ └──────────────────┘ │ |
51 | | //! │ │ │ |
52 | | //! │ ▼ │ |
53 | | //! │ ┌──────────────────┐ │ |
54 | | //! └────────────▶│ Cleanup │◀─┘ |
55 | | //! └──────────────────┘ |
56 | | //! ``` |
57 | | //! |
58 | | //! # Example |
59 | | //! |
60 | | //! ``` |
61 | | //! use ureq_proto::client::*; |
62 | | //! use ureq_proto::http::Request; |
63 | | //! |
64 | | //! let request = Request::put("https://example.test/my-path") |
65 | | //! .header("Expect", "100-continue") |
66 | | //! .header("x-foo", "bar") |
67 | | //! .body(()) |
68 | | //! .unwrap(); |
69 | | //! |
70 | | //! // ********************************** Prepare |
71 | | //! |
72 | | //! let mut call = Call::new(request).unwrap(); |
73 | | //! |
74 | | //! // Prepare with state from cookie jar. The uri |
75 | | //! // is used to key the cookies. |
76 | | //! let uri = call.uri(); |
77 | | //! |
78 | | //! // call.header("Cookie", "my_cookie1=value1"); |
79 | | //! // call.header("Cookie", "my_cookie2=value2"); |
80 | | //! |
81 | | //! // Obtain a connection for the uri, either a |
82 | | //! // pooled connection from a previous http/1.1 |
83 | | //! // keep-alive, or open a new. The connection |
84 | | //! // must be TLS wrapped if the scheme so indicate. |
85 | | //! // let connection = todo!(); |
86 | | //! |
87 | | //! // Sans-IO means it does not use any |
88 | | //! // Write trait or similar. Requests and request |
89 | | //! // bodies are written to a buffer that in turn |
90 | | //! // should be sent via the connection. |
91 | | //! let mut output = vec![0_u8; 1024]; |
92 | | //! |
93 | | //! // ********************************** SendRequest |
94 | | //! |
95 | | //! // Proceed to the next state writing the request. |
96 | | //! let mut call = call.proceed(); |
97 | | //! |
98 | | //! let output_used = call.write(&mut output).unwrap(); |
99 | | //! assert_eq!(output_used, 107); |
100 | | //! |
101 | | //! assert_eq!(&output[..output_used], b"\ |
102 | | //! PUT /my-path HTTP/1.1\r\n\ |
103 | | //! host: example.test\r\n\ |
104 | | //! transfer-encoding: chunked\r\n\ |
105 | | //! expect: 100-continue\r\n\ |
106 | | //! x-foo: bar\r\n\ |
107 | | //! \r\n"); |
108 | | //! |
109 | | //! // Check we can continue to send the body |
110 | | //! assert!(call.can_proceed()); |
111 | | //! |
112 | | //! // ********************************** Await100 |
113 | | //! |
114 | | //! // In this example, we know the next state is Await100. |
115 | | //! // A real client needs to match on the variants. |
116 | | //! let mut call = match call.proceed() { |
117 | | //! Ok(Some(SendRequestResult::Await100(v))) => v, |
118 | | //! _ => panic!(), |
119 | | //! }; |
120 | | //! |
121 | | //! // When awaiting 100, the client should run a timer and |
122 | | //! // proceed to sending the body either when the server |
123 | | //! // indicates it can receive the body, or the timer runs out. |
124 | | //! |
125 | | //! // This boolean can be checked whether there's any point |
126 | | //! // in keeping waiting for the timer to run out. |
127 | | //! assert!(call.can_keep_await_100()); |
128 | | //! |
129 | | //! let input = b"HTTP/1.1 100 Continue\r\n\r\n"; |
130 | | //! let input_used = call.try_read_100(input).unwrap(); |
131 | | //! |
132 | | //! assert_eq!(input_used, 25); |
133 | | //! assert!(!call.can_keep_await_100()); |
134 | | //! |
135 | | //! // ********************************** SendBody |
136 | | //! |
137 | | //! // Proceeding is possible regardless of whether the |
138 | | //! // can_keep_await_100() is true or false. |
139 | | //! // A real client needs to match on the variants. |
140 | | //! let mut call = match call.proceed() { |
141 | | //! Ok(Await100Result::SendBody(v)) => v, |
142 | | //! _ => panic!(), |
143 | | //! }; |
144 | | //! |
145 | | //! let (input_used, o1) = |
146 | | //! call.write(b"hello", &mut output).unwrap(); |
147 | | //! |
148 | | //! assert_eq!(input_used, 5); |
149 | | //! |
150 | | //! // When doing transfer-encoding: chunked, |
151 | | //! // the end of body must be signaled with |
152 | | //! // an empty input. This is also valid for |
153 | | //! // regular content-length body. |
154 | | //! assert!(!call.can_proceed()); |
155 | | //! |
156 | | //! let (_, o2) = call.write(&[], &mut output[o1..]).unwrap(); |
157 | | //! |
158 | | //! let output_used = o1 + o2; |
159 | | //! assert_eq!(output_used, 15); |
160 | | //! |
161 | | //! assert_eq!(&output[..output_used], b"\ |
162 | | //! 5\r\n\ |
163 | | //! hello\ |
164 | | //! \r\n\ |
165 | | //! 0\r\n\ |
166 | | //! \r\n"); |
167 | | //! |
168 | | //! assert!(call.can_proceed()); |
169 | | //! |
170 | | //! // ********************************** RecvRequest |
171 | | //! |
172 | | //! // Proceed to read the request. |
173 | | //! let mut call = call.proceed().unwrap(); |
174 | | //! |
175 | | //! let part = b"HTTP/1.1 200 OK\r\nContent-Len"; |
176 | | //! let full = b"HTTP/1.1 200 OK\r\nContent-Length: 9\r\n\r\n"; |
177 | | //! |
178 | | //! // try_response can be used repeatedly until we |
179 | | //! // get enough content including all headers. |
180 | | //! let (input_used, maybe_response) = |
181 | | //! call.try_response(part, false).unwrap(); |
182 | | //! |
183 | | //! assert_eq!(input_used, 0); |
184 | | //! assert!(maybe_response.is_none()); |
185 | | //! |
186 | | //! let (input_used, maybe_response) = |
187 | | //! call.try_response(full, false).unwrap(); |
188 | | //! |
189 | | //! assert_eq!(input_used, 38); |
190 | | //! let response = maybe_response.unwrap(); |
191 | | //! |
192 | | //! // ********************************** RecvBody |
193 | | //! |
194 | | //! // It's not possible to proceed until we |
195 | | //! // have read a response. |
196 | | //! let mut call = match call.proceed() { |
197 | | //! Some(RecvResponseResult::RecvBody(v)) => v, |
198 | | //! _ => panic!(), |
199 | | //! }; |
200 | | //! |
201 | | //! let(input_used, output_used) = |
202 | | //! call.read(b"hi there!", &mut output).unwrap(); |
203 | | //! |
204 | | //! assert_eq!(input_used, 9); |
205 | | //! assert_eq!(output_used, 9); |
206 | | //! |
207 | | //! assert_eq!(&output[..output_used], b"hi there!"); |
208 | | //! |
209 | | //! // ********************************** Cleanup |
210 | | //! |
211 | | //! let call = match call.proceed() { |
212 | | //! Some(RecvBodyResult::Cleanup(v)) => v, |
213 | | //! _ => panic!(), |
214 | | //! }; |
215 | | //! |
216 | | //! if call.must_close_connection() { |
217 | | //! // connection.close(); |
218 | | //! } else { |
219 | | //! // connection.return_to_pool(); |
220 | | //! } |
221 | | //! |
222 | | //! ``` |
223 | | |
224 | | use std::fmt; |
225 | | use std::marker::PhantomData; |
226 | | |
227 | | use http::{HeaderValue, StatusCode}; |
228 | | |
229 | | use crate::CloseReason; |
230 | | use crate::body::{BodyReader, BodyWriter}; |
231 | | use crate::util::ArrayVec; |
232 | | |
233 | | use amended::AmendedRequest; |
234 | | |
235 | | mod amended; |
236 | | |
237 | | // mod holder; |
238 | | |
239 | | #[cfg(test)] |
240 | | mod test; |
241 | | |
242 | | /// Max number of headers to parse from an HTTP response |
243 | | pub const MAX_RESPONSE_HEADERS: usize = 128; |
244 | | |
245 | | /// State types for the Call state machine. |
246 | | /// |
247 | | /// These types are used as type parameters to `Call<B, State>` to represent |
248 | | /// the current state of the HTTP request/response state machine. |
249 | | pub mod state { |
250 | | pub(crate) trait Named { |
251 | | fn name() -> &'static str; |
252 | | } |
253 | | |
254 | | macro_rules! call_state { |
255 | | ($n:tt) => { |
256 | | #[doc(hidden)] |
257 | | pub struct $n(()); |
258 | | impl Named for $n { |
259 | 0 | fn name() -> &'static str { |
260 | 0 | stringify!($n) |
261 | 0 | } Unexecuted instantiation: <ureq_proto::client::state::Prepare as ureq_proto::client::state::Named>::name Unexecuted instantiation: <ureq_proto::client::state::Await100 as ureq_proto::client::state::Named>::name Unexecuted instantiation: <ureq_proto::client::state::SendBody as ureq_proto::client::state::Named>::name Unexecuted instantiation: <ureq_proto::client::state::RecvResponse as ureq_proto::client::state::Named>::name Unexecuted instantiation: <ureq_proto::client::state::RecvBody as ureq_proto::client::state::Named>::name Unexecuted instantiation: <ureq_proto::client::state::Redirect as ureq_proto::client::state::Named>::name Unexecuted instantiation: <ureq_proto::client::state::Cleanup as ureq_proto::client::state::Named>::name Unexecuted instantiation: <ureq_proto::client::state::SendRequest as ureq_proto::client::state::Named>::name |
262 | | } |
263 | | }; |
264 | | } |
265 | | |
266 | | call_state!(Prepare); |
267 | | call_state!(SendRequest); |
268 | | call_state!(Await100); |
269 | | call_state!(SendBody); |
270 | | call_state!(RecvResponse); |
271 | | call_state!(RecvBody); |
272 | | call_state!(Redirect); |
273 | | call_state!(Cleanup); |
274 | | } |
275 | | use self::state::*; |
276 | | |
277 | | /// A state machine for an HTTP request/response cycle. |
278 | | /// |
279 | | /// This type represents a state machine that transitions through various |
280 | | /// states during the lifecycle of an HTTP request/response. |
281 | | /// |
282 | | /// The type parameters are: |
283 | | /// - `State`: The current state of the state machine (e.g., `Prepare`, `SendRequest`, etc.) |
284 | | /// |
285 | | /// See the [state graph][crate::client] in the client module documentation for a |
286 | | /// visual representation of the state transitions. |
287 | | pub struct Call<State> { |
288 | | inner: Inner, |
289 | | _ph: PhantomData<State>, |
290 | | } |
291 | | |
292 | | /// Internal state of a Call. |
293 | | /// |
294 | | /// This struct contains the actual state data for a Call, independent of the |
295 | | /// state type parameter. It's exposed as pub(crate) to allow tests to inspect |
296 | | /// the state. |
297 | | #[derive(Debug)] |
298 | | pub(crate) struct Inner { |
299 | | pub request: AmendedRequest, |
300 | | pub analyzed: bool, |
301 | | pub state: BodyState, |
302 | | pub close_reason: ArrayVec<CloseReason, 4>, |
303 | | pub force_recv_body: bool, |
304 | | pub force_send_body: bool, |
305 | | pub await_100_continue: bool, |
306 | | pub status: Option<StatusCode>, |
307 | | pub location: Option<HeaderValue>, |
308 | | } |
309 | | |
310 | | impl Inner { |
311 | 0 | fn is_redirect(&self) -> bool { |
312 | 0 | match self.status { |
313 | | // 304 is a redirect code, but it has no location header and |
314 | | // thus we don't consider it a redirection. |
315 | 0 | Some(v) => v.is_redirection() && v != StatusCode::NOT_MODIFIED, |
316 | 0 | None => false, |
317 | | } |
318 | 0 | } |
319 | | } |
320 | | |
321 | | /// State of the request/response body. |
322 | | /// |
323 | | /// This struct tracks the current phase of the request/response cycle |
324 | | /// and manages the body writers and readers. |
325 | | #[derive(Debug, Default)] |
326 | | pub(crate) struct BodyState { |
327 | | phase: RequestPhase, |
328 | | writer: BodyWriter, |
329 | | reader: Option<BodyReader>, |
330 | | allow_non_standard_methods: bool, |
331 | | stop_on_chunk_boundary: bool, |
332 | | } |
333 | | |
334 | | impl BodyState { |
335 | 0 | fn need_response_body(&self) -> bool { |
336 | 0 | !matches!( |
337 | 0 | self.reader, |
338 | | Some(BodyReader::NoBody) | Some(BodyReader::LengthDelimited(0)) |
339 | | ) |
340 | 0 | } |
341 | | } |
342 | | |
343 | | /// Phases of sending an HTTP request. |
344 | | /// |
345 | | /// This enum represents the different phases of sending an HTTP request: |
346 | | /// - `SendLine`: Sending the request line (method, path, version) |
347 | | /// - `SendHeaders`: Sending the request headers |
348 | | /// - `SendBody`: Sending the request body |
349 | | #[derive(Clone, Copy, PartialEq, Eq, Default)] |
350 | | enum RequestPhase { |
351 | | #[default] |
352 | | Line, |
353 | | Headers(usize), |
354 | | Body, |
355 | | } |
356 | | |
357 | | impl RequestPhase { |
358 | 0 | fn is_prelude(&self) -> bool { |
359 | 0 | matches!(self, RequestPhase::Line | RequestPhase::Headers(_)) |
360 | 0 | } |
361 | | |
362 | 0 | fn is_body(&self) -> bool { |
363 | 0 | matches!(self, RequestPhase::Body) |
364 | 0 | } |
365 | | } |
366 | | |
367 | | impl<S> Call<S> { |
368 | 0 | fn wrap(inner: Inner) -> Call<S> |
369 | 0 | where |
370 | 0 | S: Named, |
371 | | { |
372 | 0 | let wrapped = Call { |
373 | 0 | inner, |
374 | 0 | _ph: PhantomData, |
375 | 0 | }; |
376 | | |
377 | 0 | debug!("{:?}", wrapped); |
378 | | |
379 | 0 | wrapped |
380 | 0 | } Unexecuted instantiation: <ureq_proto::client::Call<ureq_proto::client::state::SendRequest>>::wrap Unexecuted instantiation: <ureq_proto::client::Call<ureq_proto::client::state::RecvResponse>>::wrap Unexecuted instantiation: <ureq_proto::client::Call<ureq_proto::client::state::Cleanup>>::wrap Unexecuted instantiation: <ureq_proto::client::Call<ureq_proto::client::state::Prepare>>::wrap Unexecuted instantiation: <ureq_proto::client::Call<ureq_proto::client::state::Await100>>::wrap Unexecuted instantiation: <ureq_proto::client::Call<ureq_proto::client::state::RecvBody>>::wrap Unexecuted instantiation: <ureq_proto::client::Call<ureq_proto::client::state::Redirect>>::wrap Unexecuted instantiation: <ureq_proto::client::Call<ureq_proto::client::state::SendBody>>::wrap |
381 | | |
382 | | #[cfg(test)] |
383 | | pub(crate) fn inner(&self) -> &Inner { |
384 | | &self.inner |
385 | | } |
386 | | } |
387 | | |
388 | | // //////////////////////////////////////////////////////////////////////////////////////////// PREPARE |
389 | | |
390 | | mod prepare; |
391 | | |
392 | | // //////////////////////////////////////////////////////////////////////////////////////////// SEND REQUEST |
393 | | |
394 | | mod sendreq; |
395 | | |
396 | | /// Possible state transitions after sending a request. |
397 | | /// |
398 | | /// After sending the request headers, the call can transition to one of three states: |
399 | | /// - `Await100`: If the request included an `Expect: 100-continue` header |
400 | | /// - `SendBody`: If the request has a body to send |
401 | | /// - `RecvResponse`: If the request has no body (e.g., GET, HEAD) |
402 | | /// |
403 | | /// See the [state graph][crate::client] for a visual representation. |
404 | | pub enum SendRequestResult { |
405 | | /// Expect-100/Continue mechanic. |
406 | | Await100(Call<Await100>), |
407 | | |
408 | | /// Send the request body. |
409 | | SendBody(Call<SendBody>), |
410 | | |
411 | | /// Receive the response. |
412 | | RecvResponse(Call<RecvResponse>), |
413 | | } |
414 | | |
415 | | // //////////////////////////////////////////////////////////////////////////////////////////// AWAIT 100 |
416 | | |
417 | | mod await100; |
418 | | |
419 | | /// Possible state transitions after awaiting a 100 Continue response. |
420 | | /// |
421 | | /// After awaiting a 100 Continue response, the call can transition to one of two states: |
422 | | /// - `SendBody`: If the server sent a 100 Continue response or the timeout expired |
423 | | /// - `RecvResponse`: If the server sent a different response |
424 | | /// |
425 | | /// See the [state graph][crate::client] for a visual representation. |
426 | | pub enum Await100Result { |
427 | | /// Send the request body. |
428 | | SendBody(Call<SendBody>), |
429 | | |
430 | | /// Receive server response. |
431 | | RecvResponse(Call<RecvResponse>), |
432 | | } |
433 | | |
434 | | // //////////////////////////////////////////////////////////////////////////////////////////// SEND BODY |
435 | | |
436 | | mod sendbody; |
437 | | |
438 | | // //////////////////////////////////////////////////////////////////////////////////////////// RECV RESPONSE |
439 | | |
440 | | mod recvresp; |
441 | | |
442 | | /// Possible state transitions after receiving a response. |
443 | | /// |
444 | | /// After receiving a response (status and headers), the call can transition to one of three states: |
445 | | /// - `RecvBody`: If the response has a body to receive |
446 | | /// - `Redirect`: If the response is a redirect |
447 | | /// - `Cleanup`: If the response has no body and is not a redirect |
448 | | /// |
449 | | /// See the [state graph][crate::client] for a visual representation. |
450 | | pub enum RecvResponseResult { |
451 | | /// Receive a response body. |
452 | | RecvBody(Call<RecvBody>), |
453 | | |
454 | | /// Follow a redirect. |
455 | | Redirect(Call<Redirect>), |
456 | | |
457 | | /// Run cleanup. |
458 | | Cleanup(Call<Cleanup>), |
459 | | } |
460 | | |
461 | | // //////////////////////////////////////////////////////////////////////////////////////////// RECV BODY |
462 | | |
463 | | mod recvbody; |
464 | | |
465 | | /// Possible state transitions after receiving a response body. |
466 | | /// |
467 | | /// After receiving a response body, the call can transition to one of two states: |
468 | | /// - `Redirect`: If the response is a redirect |
469 | | /// - `Cleanup`: If the response is not a redirect |
470 | | /// |
471 | | /// See the [state graph][crate::client] for a visual representation. |
472 | | pub enum RecvBodyResult { |
473 | | /// Follow a redirect |
474 | | Redirect(Call<Redirect>), |
475 | | |
476 | | /// Go to cleanup |
477 | | Cleanup(Call<Cleanup>), |
478 | | } |
479 | | |
480 | | // //////////////////////////////////////////////////////////////////////////////////////////// REDIRECT |
481 | | |
482 | | mod redirect; |
483 | | |
484 | | /// Strategy for preserving authorization headers during redirects. |
485 | | /// |
486 | | /// This enum defines how authorization headers should be handled when following |
487 | | /// redirects: |
488 | | /// |
489 | | /// * `Never`: Never preserve the `authorization` header in redirects. This is the default. |
490 | | /// * `SameHost`: Preserve the `authorization` header when the redirect is to the same host |
491 | | /// and uses the same scheme (or switches to a more secure one, i.e., from HTTP to HTTPS, |
492 | | /// but not the reverse). |
493 | | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
494 | | #[non_exhaustive] |
495 | | pub enum RedirectAuthHeaders { |
496 | | /// Never preserve the `authorization` header on redirect. This is the default. |
497 | | Never, |
498 | | /// Preserve the `authorization` header when the redirect is to the same host. Both hosts must use |
499 | | /// the same scheme (or switch to a more secure one, i.e we can redirect from `http` to `https`, |
500 | | /// but not the reverse). |
501 | | SameHost, |
502 | | } |
503 | | |
504 | | // //////////////////////////////////////////////////////////////////////////////////////////// CLEANUP |
505 | | |
506 | | impl Call<Cleanup> { |
507 | | /// Tell if we must close the connection. |
508 | 0 | pub fn must_close_connection(&self) -> bool { |
509 | 0 | self.close_reason().is_some() |
510 | 0 | } |
511 | | |
512 | | /// If we are closing the connection, give a reason. |
513 | 0 | pub fn close_reason(&self) -> Option<&'static str> { |
514 | 0 | self.inner.close_reason.first().map(|s| s.explain()) |
515 | 0 | } |
516 | | } |
517 | | |
518 | | // //////////////////////////////////////////////////////////////////////////////////////////// |
519 | | |
520 | | impl<State: Named> fmt::Debug for Call<State> { |
521 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
522 | 0 | write!(f, "Call<{}>", State::name()) |
523 | 0 | } Unexecuted instantiation: <ureq_proto::client::Call<ureq_proto::client::state::SendRequest> as core::fmt::Debug>::fmt Unexecuted instantiation: <ureq_proto::client::Call<ureq_proto::client::state::RecvResponse> as core::fmt::Debug>::fmt Unexecuted instantiation: <ureq_proto::client::Call<ureq_proto::client::state::Cleanup> as core::fmt::Debug>::fmt Unexecuted instantiation: <ureq_proto::client::Call<ureq_proto::client::state::Prepare> as core::fmt::Debug>::fmt Unexecuted instantiation: <ureq_proto::client::Call<ureq_proto::client::state::Await100> as core::fmt::Debug>::fmt Unexecuted instantiation: <ureq_proto::client::Call<ureq_proto::client::state::RecvBody> as core::fmt::Debug>::fmt Unexecuted instantiation: <ureq_proto::client::Call<ureq_proto::client::state::Redirect> as core::fmt::Debug>::fmt Unexecuted instantiation: <ureq_proto::client::Call<ureq_proto::client::state::SendBody> as core::fmt::Debug>::fmt |
524 | | } |
525 | | |
526 | | impl fmt::Debug for RequestPhase { |
527 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
528 | 0 | match self { |
529 | 0 | Self::Line => write!(f, "SendLine"), |
530 | 0 | Self::Headers(_) => write!(f, "SendHeaders"), |
531 | 0 | Self::Body => write!(f, "SendBody"), |
532 | | } |
533 | 0 | } |
534 | | } |
535 | | |
536 | | #[cfg(test)] |
537 | | mod tests { |
538 | | use super::*; |
539 | | use crate::Error; |
540 | | use crate::client::Inner; |
541 | | use crate::client::amended::AmendedRequest; |
542 | | use crate::client::state::SendRequest; |
543 | | use http::{Method, Request, Version, header}; |
544 | | use std::str; |
545 | | |
546 | | #[test] |
547 | | fn get_simple() { |
548 | | let req = Request::get("http://foo.test/page") |
549 | | .header("content-length", "0") |
550 | | .body(()) |
551 | | .unwrap(); |
552 | | |
553 | | let call = Call::new(req).unwrap(); |
554 | | |
555 | | let mut call = call.proceed(); |
556 | | |
557 | | let mut output = vec![0; 1024]; |
558 | | let n = call.write(&mut output).unwrap(); |
559 | | let s = str::from_utf8(&output[..n]).unwrap(); |
560 | | |
561 | | assert_eq!( |
562 | | s, |
563 | | "GET /page HTTP/1.1\r\n\ |
564 | | host: foo.test\r\n\ |
565 | | content-length: 0\r\n\ |
566 | | \r\n" |
567 | | ); |
568 | | |
569 | | // Since GET has no body (explicitly defined by content-length too) we should go straight to RecvResponse |
570 | | let Ok(Some(SendRequestResult::RecvResponse(_call))) = call.proceed() else { |
571 | | panic!("Exepcted `RecvResponse`") |
572 | | }; |
573 | | } |
574 | | |
575 | | #[test] |
576 | | fn head_simple() { |
577 | | let req = Request::head("http://foo.test/page").body(()).unwrap(); |
578 | | let call = Call::new(req).unwrap(); |
579 | | |
580 | | let mut call = call.proceed(); |
581 | | |
582 | | let mut output = vec![0; 1024]; |
583 | | let n = call.write(&mut output).unwrap(); |
584 | | let s = str::from_utf8(&output[..n]).unwrap(); |
585 | | |
586 | | assert_eq!(s, "HEAD /page HTTP/1.1\r\nhost: foo.test\r\n\r\n"); |
587 | | } |
588 | | |
589 | | #[test] |
590 | | fn head_without_body() { |
591 | | let req = Request::head("http://foo.test/page").body(()).unwrap(); |
592 | | let call = Call::new(req).unwrap(); |
593 | | |
594 | | let mut call = call.proceed(); |
595 | | |
596 | | let mut output = vec![0; 1024]; |
597 | | call.write(&mut output).unwrap(); |
598 | | |
599 | | // Check if we can proceed |
600 | | assert!(call.can_proceed()); |
601 | | |
602 | | // Proceed to the next state |
603 | | let call = call.proceed().unwrap().unwrap(); |
604 | | |
605 | | // For a HEAD request, we should get a RecvResponse result |
606 | | let SendRequestResult::RecvResponse(_) = call else { |
607 | | panic!("Expected RecvResponse") |
608 | | }; |
609 | | } |
610 | | |
611 | | #[test] |
612 | | fn head_with_body_despite_method() { |
613 | | let req = Request::head("http://foo.fest/page") |
614 | | .header(header::TRANSFER_ENCODING, "chunked") |
615 | | .body(()) |
616 | | .unwrap(); |
617 | | |
618 | | let mut call = Call::new(req).unwrap(); |
619 | | |
620 | | // Force sending a body despite the method |
621 | | call.force_send_body(); |
622 | | |
623 | | let mut call = call.proceed(); |
624 | | |
625 | | let mut output = vec![0; 1024]; |
626 | | call.write(&mut output).unwrap(); |
627 | | |
628 | | // Check if we can proceed |
629 | | assert!(call.can_proceed()); |
630 | | |
631 | | // Proceed to the next state |
632 | | let call = call.proceed().unwrap().unwrap(); |
633 | | let SendRequestResult::SendBody(mut call) = call else { |
634 | | panic!("Expected SendBody") |
635 | | }; |
636 | | |
637 | | // Write an empty body |
638 | | let (i, n) = call.write(&[], &mut output).unwrap(); |
639 | | assert_eq!(i, 0); |
640 | | assert_eq!(n, 5); // "0\r\n\r\n" for chunked encoding |
641 | | |
642 | | // Check if we can proceed (body is fully sent) |
643 | | assert!(call.can_proceed()); |
644 | | } |
645 | | |
646 | | #[test] |
647 | | fn post_simple() { |
648 | | let req = Request::post("http://f.test/page") |
649 | | .header("content-length", 5) |
650 | | .body(()) |
651 | | .unwrap(); |
652 | | let call = Call::new(req).unwrap(); |
653 | | |
654 | | // Proceed to SendRequest |
655 | | let mut call = call.proceed(); |
656 | | |
657 | | // Write the request headers |
658 | | let mut output = vec![0; 1024]; |
659 | | let n1 = call.write(&mut output).unwrap(); |
660 | | |
661 | | // Check if we can proceed |
662 | | assert!(call.can_proceed()); |
663 | | |
664 | | // Proceed to the next state |
665 | | let call = call.proceed().unwrap().unwrap(); |
666 | | let SendRequestResult::SendBody(mut call) = call else { |
667 | | panic!("Expected SendBody") |
668 | | }; |
669 | | |
670 | | // Write the body |
671 | | let (i1, n2) = call.write(b"hallo", &mut output[n1..]).unwrap(); |
672 | | assert_eq!(i1, 5); |
673 | | |
674 | | let s = str::from_utf8(&output[..n1 + n2]).unwrap(); |
675 | | assert_eq!( |
676 | | s, |
677 | | "POST /page HTTP/1.1\r\nhost: f.test\r\ncontent-length: 5\r\n\r\nhallo" |
678 | | ); |
679 | | } |
680 | | |
681 | | #[test] |
682 | | fn post_small_output() { |
683 | | let req = Request::post("http://f.test/page") |
684 | | .header("content-length", 5) |
685 | | .body(()) |
686 | | .unwrap(); |
687 | | let call = Call::new(req).unwrap(); |
688 | | |
689 | | // Proceed to SendRequest |
690 | | let mut call = call.proceed(); |
691 | | |
692 | | let mut output = vec![0; 1024]; |
693 | | let body = b"hallo"; |
694 | | |
695 | | // Write the request headers in multiple steps with small output buffers |
696 | | { |
697 | | let n = call.write(&mut output[..25]).unwrap(); |
698 | | let s = str::from_utf8(&output[..n]).unwrap(); |
699 | | assert_eq!(s, "POST /page HTTP/1.1\r\n"); |
700 | | assert!(!call.can_proceed()); |
701 | | } |
702 | | |
703 | | { |
704 | | let n = call.write(&mut output[..20]).unwrap(); |
705 | | let s = str::from_utf8(&output[..n]).unwrap(); |
706 | | assert_eq!(s, "host: f.test\r\n"); |
707 | | assert!(!call.can_proceed()); |
708 | | } |
709 | | |
710 | | { |
711 | | let n = call.write(&mut output[..21]).unwrap(); |
712 | | let s = str::from_utf8(&output[..n]).unwrap(); |
713 | | assert_eq!(s, "content-length: 5\r\n\r\n"); |
714 | | assert!(call.can_proceed()); |
715 | | } |
716 | | |
717 | | // Proceed to SendBody |
718 | | let call = call.proceed().unwrap().unwrap(); |
719 | | let SendRequestResult::SendBody(mut call) = call else { |
720 | | panic!("Expected SendBody") |
721 | | }; |
722 | | |
723 | | // Write the body |
724 | | { |
725 | | let (i, n) = call.write(body, &mut output[..25]).unwrap(); |
726 | | assert_eq!(n, 5); |
727 | | assert_eq!(i, 5); |
728 | | let s = str::from_utf8(&output[..n]).unwrap(); |
729 | | assert_eq!(s, "hallo"); |
730 | | |
731 | | // Check if we can proceed (body is fully sent) |
732 | | assert!(call.can_proceed()); |
733 | | } |
734 | | } |
735 | | |
736 | | #[test] |
737 | | fn post_with_short_content_length() { |
738 | | let req = Request::post("http://f.test/page") |
739 | | .header("content-length", 2) |
740 | | .body(()) |
741 | | .unwrap(); |
742 | | let call = Call::new(req).unwrap(); |
743 | | |
744 | | // Proceed to SendRequest |
745 | | let mut call = call.proceed(); |
746 | | |
747 | | // Write the request headers |
748 | | let mut output = vec![0; 1024]; |
749 | | let n1 = call.write(&mut output).unwrap(); |
750 | | |
751 | | // Check if we can proceed |
752 | | assert!(call.can_proceed()); |
753 | | |
754 | | // Proceed to SendBody |
755 | | let call = call.proceed().unwrap().unwrap(); |
756 | | let SendRequestResult::SendBody(mut call) = call else { |
757 | | panic!("Expected SendBody") |
758 | | }; |
759 | | |
760 | | // Write the body (first write should fail because it's larger than content-length) |
761 | | let body = b"hallo"; |
762 | | let r = call.write(body, &mut output[n1..]); |
763 | | assert_eq!(r.unwrap_err(), Error::BodyLargerThanContentLength); |
764 | | |
765 | | // Write a smaller body that fits within content-length |
766 | | let body = b"ha"; |
767 | | let r = call.write(body, &mut output[n1..]); |
768 | | assert!(r.is_ok()); |
769 | | |
770 | | // Check if we can proceed (body is fully sent) |
771 | | assert!(call.can_proceed()); |
772 | | } |
773 | | |
774 | | #[test] |
775 | | fn post_with_short_body_input() { |
776 | | let req = Request::post("http://f.test/page") |
777 | | .header("content-length", 5) |
778 | | .body(()) |
779 | | .unwrap(); |
780 | | let call = Call::new(req).unwrap(); |
781 | | |
782 | | // Proceed to SendRequest |
783 | | let mut call = call.proceed(); |
784 | | |
785 | | // Write the request headers |
786 | | let mut output = vec![0; 1024]; |
787 | | let n1 = call.write(&mut output).unwrap(); |
788 | | |
789 | | // Check if we can proceed |
790 | | assert!(call.can_proceed()); |
791 | | |
792 | | // Proceed to SendBody |
793 | | let call = call.proceed().unwrap().unwrap(); |
794 | | let SendRequestResult::SendBody(mut call) = call else { |
795 | | panic!("Expected SendBody") |
796 | | }; |
797 | | |
798 | | // Write the first part of the body |
799 | | let (i1, n2) = call.write(b"ha", &mut output[n1..]).unwrap(); |
800 | | assert_eq!(i1, 2); |
801 | | |
802 | | // Write the second part of the body |
803 | | let (i2, n3) = call.write(b"ha", &mut output[n1 + n2..]).unwrap(); |
804 | | assert_eq!(i2, 2); |
805 | | |
806 | | let s = str::from_utf8(&output[..n1 + n2 + n3]).unwrap(); |
807 | | assert_eq!( |
808 | | s, |
809 | | "POST /page HTTP/1.1\r\nhost: f.test\r\ncontent-length: 5\r\n\r\nhaha" |
810 | | ); |
811 | | |
812 | | // Check if we can proceed (body is not fully sent yet) |
813 | | assert!(!call.can_proceed()); |
814 | | |
815 | | // Write the third part of the body (should fail because it's larger than remaining content length) |
816 | | let err = call.write(b"llo", &mut output[n1 + n2 + n3..]).unwrap_err(); |
817 | | assert_eq!(err, Error::BodyLargerThanContentLength); |
818 | | |
819 | | // Write the last byte to complete the content length |
820 | | let (i3, n4) = call.write(b"l", &mut output[n1 + n2 + n3..]).unwrap(); |
821 | | assert_eq!(i3, 1); |
822 | | |
823 | | let s = str::from_utf8(&output[..n1 + n2 + n3 + n4]).unwrap(); |
824 | | assert_eq!( |
825 | | s, |
826 | | "POST /page HTTP/1.1\r\nhost: f.test\r\ncontent-length: 5\r\n\r\nhahal" |
827 | | ); |
828 | | |
829 | | // Check if we can proceed (body is fully sent) |
830 | | assert!(call.can_proceed()); |
831 | | } |
832 | | |
833 | | #[test] |
834 | | fn post_with_chunked() { |
835 | | let req = Request::post("http://f.test/page") |
836 | | .header("transfer-encoding", "chunked") |
837 | | .body(()) |
838 | | .unwrap(); |
839 | | let call = Call::new(req).unwrap(); |
840 | | |
841 | | // Proceed to SendRequest |
842 | | let mut call = call.proceed(); |
843 | | |
844 | | // Write the request headers |
845 | | let mut output = vec![0; 1024]; |
846 | | let n1 = call.write(&mut output).unwrap(); |
847 | | |
848 | | // Check if we can proceed |
849 | | assert!(call.can_proceed()); |
850 | | |
851 | | // Proceed to SendBody |
852 | | let call = call.proceed().unwrap().unwrap(); |
853 | | let SendRequestResult::SendBody(mut call) = call else { |
854 | | panic!("Expected SendBody") |
855 | | }; |
856 | | |
857 | | let body = b"hallo"; |
858 | | |
859 | | // Write the first chunk of the body |
860 | | let (i1, n2) = call.write(body, &mut output[n1..]).unwrap(); |
861 | | assert_eq!(i1, 5); |
862 | | |
863 | | // Write the second chunk of the body |
864 | | let (i2, n3) = call.write(body, &mut output[n1 + n2..]).unwrap(); |
865 | | assert_eq!(i2, 5); |
866 | | |
867 | | // Indicate the end of the body |
868 | | let (i3, n4) = call.write(&[], &mut output[n1 + n2 + n3..]).unwrap(); |
869 | | assert_eq!(i3, 0); |
870 | | |
871 | | let s = str::from_utf8(&output[..n1 + n2 + n3 + n4]).unwrap(); |
872 | | assert_eq!( |
873 | | s, |
874 | | "POST /page HTTP/1.1\r\nhost: f.test\r\ntransfer-encoding: chunked\r\n\r\n5\r\nhallo\r\n5\r\nhallo\r\n0\r\n\r\n" |
875 | | ); |
876 | | |
877 | | // Check if we can proceed (body is fully sent) |
878 | | assert!(call.can_proceed()); |
879 | | } |
880 | | |
881 | | #[test] |
882 | | fn post_without_body() { |
883 | | let req = Request::post("http://foo.test/page").body(()).unwrap(); |
884 | | let call = Call::new(req).unwrap(); |
885 | | |
886 | | // Proceed to SendRequest |
887 | | let mut call = call.proceed(); |
888 | | |
889 | | // Write the request headers |
890 | | let mut output = vec![0; 1024]; |
891 | | call.write(&mut output).unwrap(); |
892 | | |
893 | | // Check if we can proceed |
894 | | assert!(call.can_proceed()); |
895 | | |
896 | | // Proceed to the next state |
897 | | let call = call.proceed().unwrap().unwrap(); |
898 | | |
899 | | // For a POST request, we should get a SendBody result |
900 | | let SendRequestResult::SendBody(mut call) = call else { |
901 | | panic!("Expected SendBody"); |
902 | | }; |
903 | | |
904 | | // Check that we can't proceed without writing a body |
905 | | assert!(!call.can_proceed()); |
906 | | |
907 | | // Write an empty body |
908 | | let (i, n) = call.write(&[], &mut output).unwrap(); |
909 | | assert_eq!(i, 0); |
910 | | assert_eq!(n, 5); // "0\r\n\r\n" for chunked encoding |
911 | | |
912 | | // Check if we can proceed (body is fully sent) |
913 | | assert!(call.can_proceed()); |
914 | | } |
915 | | |
916 | | #[test] |
917 | | fn post_streaming() { |
918 | | let req = Request::post("http://f.test/page").body(()).unwrap(); |
919 | | let call = Call::new(req).unwrap(); |
920 | | |
921 | | // Proceed to SendRequest |
922 | | let mut call = call.proceed(); |
923 | | |
924 | | // Write the request headers |
925 | | let mut output = vec![0; 1024]; |
926 | | let n1 = call.write(&mut output).unwrap(); |
927 | | |
928 | | // Check if we can proceed |
929 | | assert!(call.can_proceed()); |
930 | | |
931 | | // Proceed to SendBody |
932 | | let call = call.proceed().unwrap().unwrap(); |
933 | | let SendRequestResult::SendBody(mut call) = call else { |
934 | | panic!("Expected SendBody"); |
935 | | }; |
936 | | |
937 | | // Write the first chunk of the body (using i2, n2 to match original test) |
938 | | let (i2, n2) = call.write(b"hallo", &mut output[n1..]).unwrap(); |
939 | | |
940 | | // Send end (using i3, n3 to match original test) |
941 | | let (i3, n3) = call.write(&[], &mut output[n1 + n2..]).unwrap(); |
942 | | |
943 | | // Use i1 = 0 to match original test (in Call API, i1 is not used for headers) |
944 | | let i1 = 0; |
945 | | |
946 | | // Verify the results with the same assertions as the original test |
947 | | assert_eq!(i1, 0); |
948 | | assert_eq!(i2, 5); |
949 | | assert_eq!(n1, 65); |
950 | | assert_eq!(n2, 10); |
951 | | assert_eq!(i3, 0); |
952 | | assert_eq!(n3, 5); |
953 | | |
954 | | let s = str::from_utf8(&output[..(n1 + n2 + n3)]).unwrap(); |
955 | | |
956 | | assert_eq!( |
957 | | s, |
958 | | "POST /page HTTP/1.1\r\nhost: f.test\r\ntransfer-encoding: chunked\r\n\r\n5\r\nhallo\r\n0\r\n\r\n" |
959 | | ); |
960 | | } |
961 | | |
962 | | #[test] |
963 | | fn post_streaming_with_size() { |
964 | | let req = Request::post("http://f.test/page") |
965 | | .header("content-length", "5") |
966 | | .body(()) |
967 | | .unwrap(); |
968 | | let call = Call::new(req).unwrap(); |
969 | | |
970 | | // Proceed to SendRequest |
971 | | let mut call = call.proceed(); |
972 | | |
973 | | // Write the request headers |
974 | | let mut output = vec![0; 1024]; |
975 | | let headers_n = call.write(&mut output).unwrap(); |
976 | | |
977 | | // Check if we can proceed |
978 | | assert!(call.can_proceed()); |
979 | | |
980 | | // Proceed to SendBody |
981 | | let call = call.proceed().unwrap().unwrap(); |
982 | | let SendRequestResult::SendBody(mut call) = call else { |
983 | | panic!("Expected SendBody"); |
984 | | }; |
985 | | |
986 | | // Write the body (first call) |
987 | | let (i1, n1) = call.write(b"hallo", &mut output[headers_n..]).unwrap(); |
988 | | |
989 | | // Verify the results |
990 | | assert_eq!(i1, 5); // In Call API, i1 is the number of bytes consumed from the input |
991 | | assert_eq!(n1, 5); // In Call API, n1 is the number of bytes written to the output |
992 | | |
993 | | // Check if we can proceed (body is fully sent) |
994 | | assert!(call.can_proceed()); |
995 | | |
996 | | // Try to write more data after the body is fully sent (should fail) |
997 | | let err = call |
998 | | .write(b"hallo", &mut output[headers_n + n1..]) |
999 | | .unwrap_err(); |
1000 | | assert_eq!(err, Error::BodyContentAfterFinish); |
1001 | | |
1002 | | let s = str::from_utf8(&output[..headers_n + n1]).unwrap(); |
1003 | | |
1004 | | assert_eq!( |
1005 | | s, |
1006 | | "POST /page HTTP/1.1\r\nhost: f.test\r\ncontent-length: 5\r\n\r\nhallo" |
1007 | | ); |
1008 | | } |
1009 | | |
1010 | | #[test] |
1011 | | fn post_streaming_after_end() { |
1012 | | let req = Request::post("http://f.test/page").body(()).unwrap(); |
1013 | | let call = Call::new(req).unwrap(); |
1014 | | |
1015 | | // Proceed to SendRequest |
1016 | | let mut call = call.proceed(); |
1017 | | |
1018 | | // Write the request headers |
1019 | | let mut output = vec![0; 1024]; |
1020 | | let headers_n = call.write(&mut output).unwrap(); |
1021 | | |
1022 | | // Check if we can proceed |
1023 | | assert!(call.can_proceed()); |
1024 | | |
1025 | | // Proceed to SendBody |
1026 | | let call = call.proceed().unwrap().unwrap(); |
1027 | | let SendRequestResult::SendBody(mut call) = call else { |
1028 | | panic!("Expected SendBody"); |
1029 | | }; |
1030 | | |
1031 | | // Write the body |
1032 | | let (_, n1) = call.write(b"hallo", &mut output[headers_n..]).unwrap(); |
1033 | | |
1034 | | // Send end |
1035 | | let (_, n2) = call.write(&[], &mut output[headers_n + n1..]).unwrap(); |
1036 | | |
1037 | | // Try to write after end |
1038 | | let err = call.write(b"after end", &mut output[headers_n + n1 + n2..]); |
1039 | | |
1040 | | assert_eq!(err, Err(Error::BodyContentAfterFinish)); |
1041 | | } |
1042 | | |
1043 | | #[test] |
1044 | | fn post_streaming_too_much() { |
1045 | | let req = Request::post("http://f.test/page") |
1046 | | .header("content-length", "5") |
1047 | | .body(()) |
1048 | | .unwrap(); |
1049 | | let call = Call::new(req).unwrap(); |
1050 | | |
1051 | | // Proceed to SendRequest |
1052 | | let mut call = call.proceed(); |
1053 | | |
1054 | | // Write the request headers |
1055 | | let mut output = vec![0; 1024]; |
1056 | | let headers_n = call.write(&mut output).unwrap(); |
1057 | | |
1058 | | // Check if we can proceed |
1059 | | assert!(call.can_proceed()); |
1060 | | |
1061 | | // Proceed to SendBody |
1062 | | let call = call.proceed().unwrap().unwrap(); |
1063 | | let SendRequestResult::SendBody(mut call) = call else { |
1064 | | panic!("Expected SendBody"); |
1065 | | }; |
1066 | | |
1067 | | // Write the body (first call) |
1068 | | let (i1, n1) = call.write(b"hallo", &mut output[headers_n..]).unwrap(); |
1069 | | |
1070 | | // Verify the results |
1071 | | assert_eq!(i1, 5); // In Call API, i1 is the number of bytes consumed from the input |
1072 | | assert_eq!(n1, 5); // In Call API, n1 is the number of bytes written to the output |
1073 | | |
1074 | | // Check if we can proceed (body is fully sent) |
1075 | | assert!(call.can_proceed()); |
1076 | | |
1077 | | // Try to write more data after the body is fully sent (should fail with BodyContentAfterFinish) |
1078 | | let err = call |
1079 | | .write(b"hallo", &mut output[headers_n + n1..]) |
1080 | | .unwrap_err(); |
1081 | | assert_eq!(err, Error::BodyContentAfterFinish); |
1082 | | |
1083 | | let s = str::from_utf8(&output[..headers_n + n1]).unwrap(); |
1084 | | |
1085 | | assert_eq!( |
1086 | | s, |
1087 | | "POST /page HTTP/1.1\r\nhost: f.test\r\ncontent-length: 5\r\n\r\nhallo" |
1088 | | ); |
1089 | | } |
1090 | | |
1091 | | #[test] |
1092 | | fn username_password_uri() { |
1093 | | let req = Request::get("http://martin:secret@f.test/page") |
1094 | | .body(()) |
1095 | | .unwrap(); |
1096 | | let call = Call::new(req).unwrap(); |
1097 | | |
1098 | | // Proceed to SendRequest |
1099 | | let mut call = call.proceed(); |
1100 | | |
1101 | | // Write the request headers |
1102 | | let mut output = vec![0; 1024]; |
1103 | | let n = call.write(&mut output).unwrap(); |
1104 | | |
1105 | | let s = str::from_utf8(&output[..n]).unwrap(); |
1106 | | |
1107 | | assert_eq!( |
1108 | | s, |
1109 | | "GET /page HTTP/1.1\r\nhost: f.test\r\n\ |
1110 | | authorization: Basic bWFydGluOnNlY3JldA==\r\n\r\n" |
1111 | | ); |
1112 | | } |
1113 | | |
1114 | | #[test] |
1115 | | fn username_uri() { |
1116 | | let req = Request::get("http://martin@f.test/page").body(()).unwrap(); |
1117 | | let call = Call::new(req).unwrap(); |
1118 | | |
1119 | | // Proceed to SendRequest |
1120 | | let mut call = call.proceed(); |
1121 | | |
1122 | | // Write the request headers |
1123 | | let mut output = vec![0; 1024]; |
1124 | | let n = call.write(&mut output).unwrap(); |
1125 | | |
1126 | | let s = str::from_utf8(&output[..n]).unwrap(); |
1127 | | |
1128 | | assert_eq!( |
1129 | | s, |
1130 | | "GET /page HTTP/1.1\r\nhost: f.test\r\n\ |
1131 | | authorization: Basic bWFydGluOg==\r\n\r\n" |
1132 | | ); |
1133 | | } |
1134 | | |
1135 | | #[test] |
1136 | | fn password_uri() { |
1137 | | let req = Request::get("http://:secret@f.test/page").body(()).unwrap(); |
1138 | | let call = Call::new(req).unwrap(); |
1139 | | |
1140 | | // Proceed to SendRequest |
1141 | | let mut call = call.proceed(); |
1142 | | |
1143 | | // Write the request headers |
1144 | | let mut output = vec![0; 1024]; |
1145 | | let n = call.write(&mut output).unwrap(); |
1146 | | |
1147 | | let s = str::from_utf8(&output[..n]).unwrap(); |
1148 | | |
1149 | | assert_eq!( |
1150 | | s, |
1151 | | "GET /page HTTP/1.1\r\nhost: f.test\r\n\ |
1152 | | authorization: Basic OnNlY3JldA==\r\n\r\n" |
1153 | | ); |
1154 | | } |
1155 | | |
1156 | | #[test] |
1157 | | fn override_auth_header() { |
1158 | | let req = Request::get("http://martin:secret@f.test/page") |
1159 | | // This should override the auth from the URI |
1160 | | .header("authorization", "meh meh meh") |
1161 | | .body(()) |
1162 | | .unwrap(); |
1163 | | let call = Call::new(req).unwrap(); |
1164 | | |
1165 | | // Proceed to SendRequest |
1166 | | let mut call = call.proceed(); |
1167 | | |
1168 | | // Write the request headers |
1169 | | let mut output = vec![0; 1024]; |
1170 | | let n = call.write(&mut output).unwrap(); |
1171 | | |
1172 | | let s = str::from_utf8(&output[..n]).unwrap(); |
1173 | | |
1174 | | assert_eq!( |
1175 | | s, |
1176 | | "GET /page HTTP/1.1\r\nhost: f.test\r\n\ |
1177 | | authorization: meh meh meh\r\n\r\n" |
1178 | | ); |
1179 | | } |
1180 | | |
1181 | | #[test] |
1182 | | fn non_standard_port() { |
1183 | | let req = Request::get("http://f.test:8080/page").body(()).unwrap(); |
1184 | | let call = Call::new(req).unwrap(); |
1185 | | |
1186 | | // Proceed to SendRequest |
1187 | | let mut call = call.proceed(); |
1188 | | |
1189 | | // Write the request headers |
1190 | | let mut output = vec![0; 1024]; |
1191 | | let n = call.write(&mut output).unwrap(); |
1192 | | |
1193 | | let s = str::from_utf8(&output[..n]).unwrap(); |
1194 | | |
1195 | | assert_eq!(s, "GET /page HTTP/1.1\r\nhost: f.test:8080\r\n\r\n"); |
1196 | | } |
1197 | | |
1198 | | #[test] |
1199 | | fn non_standard_method_not_allowed() { |
1200 | | let m = Method::from_bytes(b"FNORD").unwrap(); |
1201 | | |
1202 | | let req = Request::builder() |
1203 | | .method(m.clone()) |
1204 | | .uri("http://f.test:8080/page") |
1205 | | .body(()) |
1206 | | .unwrap(); |
1207 | | |
1208 | | let call = Call::new(req).unwrap(); |
1209 | | |
1210 | | // Proceed to SendRequest |
1211 | | let mut call = call.proceed(); |
1212 | | |
1213 | | // Try to write the request headers |
1214 | | let mut output = vec![0; 1024]; |
1215 | | let err = call.write(&mut output).unwrap_err(); |
1216 | | |
1217 | | assert_eq!(err, Error::MethodVersionMismatch(m, Version::HTTP_11)); |
1218 | | } |
1219 | | |
1220 | | #[test] |
1221 | | fn non_standard_method_when_allowed() { |
1222 | | let m = Method::from_bytes(b"FNORD").unwrap(); |
1223 | | |
1224 | | let req = Request::builder() |
1225 | | .method(m.clone()) |
1226 | | .uri("http://f.test:8080/page") |
1227 | | .body(()) |
1228 | | .unwrap(); |
1229 | | |
1230 | | let mut call = Call::new(req).unwrap(); |
1231 | | |
1232 | | // Allow non-standard methods |
1233 | | call.allow_non_standard_methods(true); |
1234 | | |
1235 | | // Proceed to SendRequest |
1236 | | let mut call = call.proceed(); |
1237 | | |
1238 | | // Write the request headers |
1239 | | let mut output = vec![0; 1024]; |
1240 | | let n = call.write(&mut output).unwrap(); |
1241 | | |
1242 | | let s = str::from_utf8(&output[..n]).unwrap(); |
1243 | | |
1244 | | assert_eq!(s, "FNORD /page HTTP/1.1\r\nhost: f.test:8080\r\n\r\n"); |
1245 | | } |
1246 | | |
1247 | | #[test] |
1248 | | fn ensure_reasonable_stack_sizes() { |
1249 | | macro_rules! ensure { |
1250 | | ($type:ty, $size:tt) => { |
1251 | | let sz = std::mem::size_of::<$type>(); |
1252 | | assert!( |
1253 | | sz <= $size, |
1254 | | "Stack size of {} is too big {} > {}", |
1255 | | stringify!($type), |
1256 | | sz, |
1257 | | $size |
1258 | | ); |
1259 | | }; |
1260 | | } |
1261 | | |
1262 | | ensure!(http::Request<()>, 300); // 224 |
1263 | | ensure!(AmendedRequest, 400); // 368 |
1264 | | ensure!(Inner, 600); // 512 |
1265 | | ensure!(Call<SendRequest>, 600); // 512 |
1266 | | } |
1267 | | |
1268 | | #[test] |
1269 | | fn connect() { |
1270 | | let req = Request::builder() |
1271 | | .method(Method::CONNECT) |
1272 | | .uri("http://example.com:80") |
1273 | | .body(()) |
1274 | | .unwrap(); |
1275 | | |
1276 | | let call = Call::new(req).unwrap(); |
1277 | | let mut call = call.proceed(); |
1278 | | |
1279 | | let mut output = vec![0; 1024]; |
1280 | | let n = call.write(&mut output).unwrap(); |
1281 | | |
1282 | | // CONNECT request should have request target in authority form |
1283 | | let s = str::from_utf8(&output[..n]).unwrap(); |
1284 | | assert_eq!( |
1285 | | s, |
1286 | | "CONNECT example.com:80 HTTP/1.1\r\nhost: example.com:80\r\n\r\n" |
1287 | | ); |
1288 | | |
1289 | | // Should go to RecvResponse state (content-length/transfer-encoding is ignored with CONNECT) |
1290 | | let SendRequestResult::RecvResponse(mut call) = call.proceed().unwrap().unwrap() else { |
1291 | | panic!("Expected RecvResponse state") |
1292 | | }; |
1293 | | |
1294 | | let input = b"HTTP/1.1 200 OK\r\n\r\n"; |
1295 | | let (n, res) = call.try_response(input, false).unwrap(); |
1296 | | assert_eq!(n, 19); |
1297 | | |
1298 | | let Some(res) = res else { |
1299 | | panic!("`try_response()` should return a response"); |
1300 | | }; |
1301 | | |
1302 | | assert!(res.headers().is_empty()); |
1303 | | |
1304 | | // should go to Cleanup state (content-length/transfer-encoding is ignored with CONNECT) |
1305 | | let RecvResponseResult::Cleanup(_call) = call.proceed().unwrap() else { |
1306 | | panic!("Expected Cleanup state") |
1307 | | }; |
1308 | | } |
1309 | | |
1310 | | #[test] |
1311 | | fn connect_read_body() { |
1312 | | let req = Request::builder() |
1313 | | .method(Method::CONNECT) |
1314 | | .uri("http://example.com:80") |
1315 | | .body(()) |
1316 | | .unwrap(); |
1317 | | |
1318 | | let call = Call::new(req).unwrap(); |
1319 | | let mut call = call.proceed(); |
1320 | | |
1321 | | let mut output = vec![0; 1024]; |
1322 | | let n = call.write(&mut output).unwrap(); |
1323 | | |
1324 | | // CONNECT request should have request target in authority form |
1325 | | let s = str::from_utf8(&output[..n]).unwrap(); |
1326 | | assert_eq!( |
1327 | | s, |
1328 | | "CONNECT example.com:80 HTTP/1.1\r\nhost: example.com:80\r\n\r\n" |
1329 | | ); |
1330 | | |
1331 | | // Should go to RecvResponse state (content-length/transfer-encoding is ignored with CONNECT) |
1332 | | let SendRequestResult::RecvResponse(mut call) = call.proceed().unwrap().unwrap() else { |
1333 | | panic!("Expected RecvResponse state") |
1334 | | }; |
1335 | | |
1336 | | call.force_recv_body(); |
1337 | | |
1338 | | let input = b"HTTP/1.1 200 OK\r\ncontent-length: 1024\r\n\r\n"; |
1339 | | let (_n, res) = call.try_response(input, false).unwrap(); |
1340 | | |
1341 | | let Some(_res) = res else { |
1342 | | panic!("`try_response()` should return a response"); |
1343 | | }; |
1344 | | |
1345 | | let RecvResponseResult::RecvBody(_call) = call.proceed().unwrap() else { |
1346 | | panic!("Expect RecvBody state"); |
1347 | | }; |
1348 | | } |
1349 | | |
1350 | | #[test] |
1351 | | fn connect_send_body_fails() { |
1352 | | let req = Request::builder() |
1353 | | .method(Method::CONNECT) |
1354 | | .uri("http://example.com:80") |
1355 | | .header("content-length", 1024) |
1356 | | .body(()) |
1357 | | .unwrap(); |
1358 | | |
1359 | | let call = Call::new(req).unwrap(); |
1360 | | let mut call = call.proceed(); |
1361 | | |
1362 | | let mut output = vec![0; 1024]; |
1363 | | call.write(&mut output) |
1364 | | .expect_err("no body allowed on CONNECT request"); |
1365 | | } |
1366 | | |
1367 | | #[test] |
1368 | | fn connect_send_body_with_footgun() { |
1369 | | let req = Request::builder() |
1370 | | .method(Method::CONNECT) |
1371 | | .uri("http://example.com:80") |
1372 | | .header("content-length", 1024) |
1373 | | .body(()) |
1374 | | .unwrap(); |
1375 | | |
1376 | | let mut call = Call::new(req).unwrap(); |
1377 | | |
1378 | | call.force_send_body(); |
1379 | | let mut call = call.proceed(); |
1380 | | |
1381 | | let mut output = vec![0; 1024]; |
1382 | | let n = call.write(&mut output).unwrap(); |
1383 | | |
1384 | | // CONNECT request should have request target in authority form |
1385 | | let s = str::from_utf8(&output[..n]).unwrap(); |
1386 | | assert_eq!( |
1387 | | s, |
1388 | | "CONNECT example.com:80 HTTP/1.1\r\nhost: example.com:80\r\ncontent-length: 1024\r\n\r\n" |
1389 | | ); |
1390 | | |
1391 | | let SendRequestResult::SendBody(_call) = call.proceed().unwrap().unwrap() else { |
1392 | | panic!("Expected SendBody state") |
1393 | | }; |
1394 | | } |
1395 | | |
1396 | | #[test] |
1397 | | fn query_no_slash() { |
1398 | | let req = Request::get("http://foo.test?query=foo").body(()).unwrap(); |
1399 | | |
1400 | | let call = Call::new(req).unwrap(); |
1401 | | |
1402 | | let mut call = call.proceed(); |
1403 | | |
1404 | | let mut output = vec![0; 1024]; |
1405 | | let n = call.write(&mut output).unwrap(); |
1406 | | let s = str::from_utf8(&output[..n]).unwrap(); |
1407 | | |
1408 | | // The added / here is to be compliant with RFC 9112 §3.2.1 |
1409 | | assert_eq!( |
1410 | | s, |
1411 | | "GET /?query=foo HTTP/1.1\r\n\ |
1412 | | host: foo.test\r\n\ |
1413 | | \r\n" |
1414 | | ); |
1415 | | } |
1416 | | } |