http_net/message.rs
1//! Outgoing messages: the `Response` a server sends and the `Request` a
2//! client sends.
3//!
4//! Both builders validate as they go and remember the first problem instead
5//! of failing each call, so a message can be built in one chained
6//! expression; the problem surfaces as an error when the connection writes
7//! the message. Framing headers (`content-length`, `transfer-encoding`,
8//! `connection`, `upgrade`) are owned by the connection, which derives them
9//! from the body and the exchange; setting them by hand is refused, which
10//! makes conflicting framing on the way out impossible.
11
12use alloc::{borrow::Cow, format, string::String, vec::Vec};
13use core::fmt;
14
15use crate::{
16 error::HttpError,
17 forward::is_hop_by_hop,
18 head::{BodyFraming, RequestHead, check_target, is_h2c},
19 method::Method,
20 redact::{SerializedFields, serialized_fields},
21 scan::{check_field_value, is_protocol, is_token, trim_ows},
22 write::Output,
23};
24
25/// How an outgoing message's body is supplied.
26#[derive(Clone, Debug, PartialEq, Eq)]
27pub(crate) enum OutBody {
28 /// The whole body, sent with `content-length`.
29 Full(Cow<'static, [u8]>),
30 /// Streamed afterwards with exactly this many bytes.
31 Length(u64),
32 /// Streamed afterwards with chunked encoding.
33 Chunked,
34}
35
36/// Fields RFC 9110 section 6.5.1 keeps out of trailers: those that frame
37/// the message, route or authenticate it, modify the request, control the
38/// response, or describe the content. A recipient may process a trailer
39/// after the body, too late for any of them. Reviewed once per version.
40const TRAILER_FORBIDDEN: &[&str] = &[
41 "content-length",
42 "transfer-encoding",
43 "host",
44 "connection",
45 "keep-alive",
46 "te",
47 "trailer",
48 "upgrade",
49 "proxy-connection",
50 "expect",
51 "max-forwards",
52 "range",
53 "if-match",
54 "if-none-match",
55 "if-modified-since",
56 "if-unmodified-since",
57 "if-range",
58 "authorization",
59 "proxy-authorization",
60 "www-authenticate",
61 "proxy-authenticate",
62 "cookie",
63 "set-cookie",
64 "content-type",
65 "content-encoding",
66 "content-range",
67 "cache-control",
68 "expires",
69 "date",
70 "location",
71 "retry-after",
72 "vary",
73 "age",
74];
75
76/// Validates outgoing trailer fields (all of them, before anything is
77/// written), then appends the last chunk and the trailer section:
78/// `0\r\n`, the fields, and the blank line.
79pub(crate) fn push_last_chunk_with_trailers<O: Output + ?Sized>(
80 out: &mut O,
81 trailers: &[(&str, &[u8])],
82) -> Result<(), HttpError> {
83 check_trailers(trailers)?;
84 out.put(b"0\r\n");
85 for &(name, value) in trailers {
86 write_field(out, name, value);
87 }
88 out.put(b"\r\n");
89 Ok(())
90}
91
92/// Checks the protocol of a `101`: a token or a `name/version` product
93/// token, never `h2c` (RFC 9113 section 3.1).
94pub(crate) fn check_upgrade_protocol(protocol: &[u8]) -> Result<(), HttpError> {
95 if !is_protocol(protocol) {
96 Err(HttpError::InvalidHeaderValue)
97 } else if is_h2c(protocol) {
98 Err(HttpError::UpgradeNotOffered)
99 } else {
100 Ok(())
101 }
102}
103
104/// Checks one outgoing response field with the rules of
105/// [`Response::header`]; returns whether it is the `date` field.
106pub(crate) fn check_response_field(name: &str, value: &[u8]) -> Result<bool, HttpError> {
107 check_field(name, value, RESPONSE_RESERVED)?;
108 Ok(name.eq_ignore_ascii_case("date"))
109}
110
111/// Validates outgoing trailer fields without writing them. Also used for a
112/// body whose trailers are dropped (HTTP/1.0 close-delimited, `HEAD`), so a
113/// call fails the same way whatever framing the peer forced.
114pub(crate) fn check_trailers(trailers: &[(&str, &[u8])]) -> Result<(), HttpError> {
115 for &(name, value) in trailers {
116 if TRAILER_FORBIDDEN
117 .iter()
118 .any(|f| f.eq_ignore_ascii_case(name))
119 {
120 return Err(HttpError::TrailerNotAllowed);
121 }
122 check_field(name, value, &[])?;
123 }
124 Ok(())
125}
126
127/// Checks one outgoing field: a token name that is not `reserved`, and a
128/// value without control bytes or surrounding whitespace.
129fn check_field(name: &str, value: &[u8], reserved: &[&str]) -> Result<(), HttpError> {
130 if !is_token(name.as_bytes()) {
131 return Err(HttpError::InvalidHeaderName);
132 }
133 if reserved.iter().any(|r| r.eq_ignore_ascii_case(name)) {
134 return Err(HttpError::ReservedHeader);
135 }
136 if trim_ows(value).len() != value.len() {
137 // Surrounding whitespace would be stripped by the recipient; refusing
138 // it keeps what is sent equal to what is received.
139 return Err(HttpError::InvalidHeaderValue);
140 }
141 check_field_value(value)
142}
143
144pub(crate) fn write_field<O: Output + ?Sized>(out: &mut O, name: &str, value: &[u8]) {
145 out.put(name.as_bytes());
146 out.put(b": ");
147 out.put(value);
148 out.put(b"\r\n");
149}
150
151/// Validates a field and appends `name: value\r\n`.
152fn push_field(
153 fields: &mut Vec<u8>,
154 name: &str,
155 value: &[u8],
156 reserved: &[&str],
157) -> Result<(), HttpError> {
158 check_field(name, value, reserved)?;
159 write_field(fields, name, value);
160 Ok(())
161}
162
163/// Fields the connection owns: framing, connection management, and the
164/// hop-by-hop fields of RFC 9110 section 7.6.1. A response also may not set
165/// `te`, which only a request can carry (section 10.1.4).
166const RESPONSE_RESERVED: &[&str] = &[
167 "content-length",
168 "transfer-encoding",
169 "connection",
170 "upgrade",
171 "trailer",
172 "keep-alive",
173 "proxy-connection",
174 "te",
175];
176const REQUEST_RESERVED: &[&str] = &[
177 "content-length",
178 "transfer-encoding",
179 "connection",
180 "upgrade",
181 "trailer",
182 "keep-alive",
183 "proxy-connection",
184];
185
186/// A response for a [`ServerConn`](crate::ServerConn) to send.
187///
188/// The status line, `content-length` / `transfer-encoding`, and
189/// `connection` are written by the connection, which knows the request's
190/// method and version: a `HEAD` request gets the headers without the body,
191/// an HTTP/1.0 client never gets chunked encoding, and `connection: close`
192/// is added whenever the connection will close.
193///
194/// # Examples
195///
196/// ```
197/// use http_net::Response;
198///
199/// let ok = Response::text(200, "hello");
200/// assert_eq!(ok.status(), 200);
201///
202/// let asset = Response::new(200)
203/// .header("content-type", "text/css")
204/// .header("cache-control", "max-age=31536000, immutable")
205/// .body(&b"body { margin: 0 }"[..]);
206/// assert_eq!(asset.header_value("content-type"), Some(&b"text/css"[..]));
207/// ```
208#[derive(Clone, PartialEq, Eq)]
209pub struct Response {
210 status: u16,
211 fields: Vec<u8>,
212 pub(crate) body: OutBody,
213 pub(crate) close: bool,
214 pub(crate) upgrade: bool,
215 pub(crate) has_date: bool,
216 error: Option<HttpError>,
217}
218
219impl Response {
220 /// A response with this status, no extra headers, and an empty body.
221 ///
222 /// The status is validated when the response is written (`100..=599`,
223 /// and only `101` among the 1xx codes).
224 ///
225 /// # Examples
226 ///
227 /// ```
228 /// let r = http_net::Response::new(204);
229 /// assert_eq!(r.status(), 204);
230 /// ```
231 #[must_use]
232 pub fn new(status: u16) -> Self {
233 Self {
234 status,
235 fields: Vec::new(),
236 body: OutBody::Full(Cow::Borrowed(&[])),
237 close: false,
238 upgrade: false,
239 has_date: false,
240 error: None,
241 }
242 }
243
244 /// A `text/plain; charset=utf-8` response with `text` as its body.
245 ///
246 /// # Examples
247 ///
248 /// ```
249 /// let r = http_net::Response::text(404, "no such page");
250 /// assert_eq!(r.body_bytes(), Some(&b"no such page"[..]));
251 /// ```
252 #[must_use]
253 pub fn text(status: u16, text: &str) -> Self {
254 Self::new(status)
255 .header("content-type", "text/plain; charset=utf-8")
256 .body(text.as_bytes().to_vec())
257 }
258
259 /// The response a server sends after [`ServerConn::poll`](crate::ServerConn::poll)
260 /// returned a parse error: the status from [`HttpError::status`] (400
261 /// when it has none), a short `text/plain` body with the error's
262 /// one-line description, and [`close`](Self::close). The text is static
263 /// and never includes the bytes that were refused.
264 ///
265 /// # Examples
266 ///
267 /// ```
268 /// use http_net::{HttpError, Response, ServerConn};
269 ///
270 /// let r = Response::from_error(&HttpError::HeadersTooLarge);
271 /// assert_eq!(r.status(), 431);
272 /// assert!(r.closes());
273 ///
274 /// let mut conn = ServerConn::new();
275 /// let _ = conn.feed(b"GET / HTTP/1.1\r\n\r\n");
276 /// let err = conn.poll().unwrap_err();
277 /// let mut out = Vec::new();
278 /// conn.respond(&Response::from_error(&err), &mut out).unwrap();
279 /// assert!(out.starts_with(b"HTTP/1.1 400 Bad Request\r\n"));
280 /// ```
281 #[must_use]
282 pub fn from_error(error: &HttpError) -> Self {
283 Self::new(error.status().unwrap_or(400))
284 .header("content-type", "text/plain; charset=utf-8")
285 .body(format!("{error}").into_bytes())
286 .close()
287 }
288
289 /// A `101 Switching Protocols` response handing the connection to
290 /// `protocol` (for example `websocket`, or a `name/version` product
291 /// token). The connection writes `connection: upgrade` and
292 /// `upgrade: <protocol>`; add any protocol-specific headers (such as
293 /// `sec-websocket-accept`) with [`header`](Self::header).
294 ///
295 /// The protocol must be one the request offered (RFC 9110 section 7.8),
296 /// compared ASCII case-insensitively; a name without a version matches
297 /// an offer of that name with any version. Otherwise, and always for
298 /// `h2c` (RFC 9113 section 3.1), writing it fails with
299 /// [`HttpError::UpgradeNotOffered`].
300 ///
301 /// # Examples
302 ///
303 /// ```
304 /// let r = http_net::Response::switching_protocols("websocket")
305 /// .header("sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=");
306 /// assert_eq!(r.status(), 101);
307 /// assert_eq!(r.header_value("upgrade"), Some(&b"websocket"[..]));
308 /// ```
309 #[must_use]
310 pub fn switching_protocols(protocol: &str) -> Self {
311 let mut r = Self::new(101);
312 r.upgrade = true;
313 if let Err(e) = check_upgrade_protocol(protocol.as_bytes()) {
314 r.error = Some(e);
315 return r;
316 }
317 r.fields
318 .extend_from_slice(b"connection: upgrade\r\nupgrade: ");
319 r.fields.extend_from_slice(protocol.as_bytes());
320 r.fields.extend_from_slice(b"\r\n");
321 r
322 }
323
324 /// Adds a header field. The name must be a token and the value may not
325 /// contain control characters other than tab, nor leading or trailing
326 /// whitespace. Framing headers are refused with
327 /// [`HttpError::ReservedHeader`]; the first problem is reported when the
328 /// response is written.
329 ///
330 /// # Examples
331 ///
332 /// ```
333 /// use http_net::{HttpError, Response, ServerConn};
334 ///
335 /// let bad = Response::new(200).header("content-length", "5");
336 /// let mut conn = ServerConn::new();
337 /// let _ = conn.feed(b"GET / HTTP/1.1\r\nHost: a\r\n\r\n");
338 /// let _ = conn.poll();
339 /// assert_eq!(conn.respond(&bad, &mut Vec::new()), Err(HttpError::ReservedHeader));
340 /// ```
341 #[must_use]
342 pub fn header(mut self, name: &str, value: impl AsRef<[u8]>) -> Self {
343 if self.error.is_none() {
344 let value = value.as_ref();
345 if let Err(e) = push_field(&mut self.fields, name, value, RESPONSE_RESERVED) {
346 self.error = Some(e);
347 } else if name.eq_ignore_ascii_case("date") {
348 self.has_date = true;
349 }
350 }
351 self
352 }
353
354 /// Sets the whole body; it is sent with `content-length`. Static data
355 /// (an embedded asset) is borrowed rather than copied.
356 ///
357 /// # Examples
358 ///
359 /// ```
360 /// static PAGE: &[u8] = b"<!doctype html><title>x</title>";
361 /// let r = http_net::Response::new(200).body(PAGE);
362 /// assert_eq!(r.body_bytes(), Some(PAGE));
363 /// ```
364 #[must_use]
365 pub fn body(mut self, body: impl Into<Cow<'static, [u8]>>) -> Self {
366 self.body = OutBody::Full(body.into());
367 self
368 }
369
370 /// Streams the body with chunked encoding: after
371 /// [`ServerConn::respond`](crate::ServerConn::respond), send data with
372 /// [`ServerConn::send_body`](crate::ServerConn::send_body) and end it
373 /// with [`ServerConn::end_body`](crate::ServerConn::end_body). For an
374 /// HTTP/1.0 client the connection falls back to closing the connection
375 /// to end the body.
376 ///
377 /// # Examples
378 ///
379 /// ```
380 /// let r = http_net::Response::new(200).chunked();
381 /// assert_eq!(r.body_bytes(), None);
382 /// ```
383 #[must_use]
384 pub fn chunked(mut self) -> Self {
385 self.body = OutBody::Chunked;
386 self
387 }
388
389 /// Streams a body of exactly `len` bytes, sent with `content-length`;
390 /// sending more or fewer is [`HttpError::BodyLengthMismatch`].
391 ///
392 /// # Examples
393 ///
394 /// ```
395 /// let r = http_net::Response::new(200).content_length(1 << 20);
396 /// assert_eq!(r.body_bytes(), None);
397 /// ```
398 #[must_use]
399 pub fn content_length(mut self, len: u64) -> Self {
400 self.body = OutBody::Length(len);
401 self
402 }
403
404 /// Closes the connection after this response.
405 ///
406 /// # Examples
407 ///
408 /// ```
409 /// let r = http_net::Response::new(503).close();
410 /// assert!(r.closes());
411 /// ```
412 #[must_use]
413 pub fn close(mut self) -> Self {
414 self.close = true;
415 self
416 }
417
418 /// Resets the response to `Response::new(status)` while keeping the
419 /// allocation of its field storage, so one `Response` per connection
420 /// can be reused without allocating for every request. An owned body is
421 /// dropped.
422 ///
423 /// # Examples
424 ///
425 /// ```
426 /// use http_net::Response;
427 ///
428 /// let mut r = Response::new(200).header("x-request", "1").close();
429 /// r.clear(204);
430 /// assert_eq!(r.status(), 204);
431 /// assert_eq!(r.header_value("x-request"), None);
432 /// assert!(!r.closes());
433 /// r = r.header("x-request", "2"); // reuses the field storage
434 /// assert_eq!(r.header_value("x-request"), Some(&b"2"[..]));
435 /// ```
436 pub fn clear(&mut self, status: u16) {
437 self.status = status;
438 self.fields.clear();
439 self.body = OutBody::Full(Cow::Borrowed(&[]));
440 self.close = false;
441 self.upgrade = false;
442 self.has_date = false;
443 self.error = None;
444 }
445
446 /// The status code.
447 ///
448 /// # Examples
449 ///
450 /// ```
451 /// assert_eq!(http_net::Response::new(418).status(), 418);
452 /// ```
453 #[must_use]
454 pub fn status(&self) -> u16 {
455 self.status
456 }
457
458 /// Whether [`close`](Self::close) was requested.
459 ///
460 /// # Examples
461 ///
462 /// ```
463 /// assert!(!http_net::Response::new(200).closes());
464 /// ```
465 #[must_use]
466 pub fn closes(&self) -> bool {
467 self.close
468 }
469
470 /// The full body, or `None` for a streamed one.
471 ///
472 /// # Examples
473 ///
474 /// ```
475 /// assert_eq!(http_net::Response::new(200).body_bytes(), Some(&[][..]));
476 /// ```
477 #[must_use]
478 pub fn body_bytes(&self) -> Option<&[u8]> {
479 match &self.body {
480 OutBody::Full(b) => Some(b),
481 _ => None,
482 }
483 }
484
485 /// The value of the first header named `name` added to this response
486 /// (ASCII case-insensitive), including the `upgrade` of
487 /// [`switching_protocols`](Self::switching_protocols).
488 ///
489 /// # Examples
490 ///
491 /// ```
492 /// let r = http_net::Response::new(200).header("x-id", "7");
493 /// assert_eq!(r.header_value("X-ID"), Some(&b"7"[..]));
494 /// ```
495 #[must_use]
496 pub fn header_value(&self, name: &str) -> Option<&[u8]> {
497 field_value(&self.fields, name)
498 }
499
500 pub(crate) fn error(&self) -> Option<HttpError> {
501 self.error
502 }
503
504 pub(crate) fn fields(&self) -> &[u8] {
505 &self.fields
506 }
507
508 /// The protocol a `101` built with
509 /// [`switching_protocols`](Self::switching_protocols) switches to.
510 pub(crate) fn upgrade_protocol(&self) -> Option<&[u8]> {
511 if self.upgrade {
512 self.header_value("upgrade")
513 } else {
514 None
515 }
516 }
517}
518
519/// Shows status, fields, body size, and flags; the values of credential
520/// fields (`set-cookie` above all) are replaced by `<redacted N bytes>`.
521impl fmt::Debug for Response {
522 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
523 f.debug_struct("Response")
524 .field("status", &self.status)
525 .field("fields", &SerializedFields(&self.fields))
526 .field("body", &body_debug(&self.body))
527 .field("close", &self.close)
528 .field("error", &self.error)
529 .finish()
530 }
531}
532
533/// A request for a [`ClientConn`](crate::ClientConn) to send.
534///
535/// HTTP/1.1 requires a `Host` field; the connection refuses to send a
536/// request without one. The request line always says HTTP/1.1.
537///
538/// # Examples
539///
540/// ```
541/// use http_net::Request;
542///
543/// let get = Request::get("/index.html").header("host", "example.com");
544/// let post = Request::post("/api")
545/// .header("host", "example.com")
546/// .header("content-type", "application/json")
547/// .body(br#"{"ok":true}"#.to_vec());
548/// assert_eq!(get.method(), "GET");
549/// assert_eq!(post.target(), "/api");
550/// ```
551#[derive(Clone, PartialEq, Eq)]
552pub struct Request {
553 method: Cow<'static, str>,
554 pub(crate) method_kind: Method,
555 target: String,
556 fields: Vec<u8>,
557 pub(crate) body: OutBody,
558 pub(crate) close: bool,
559 pub(crate) upgrade: bool,
560 pub(crate) has_host: bool,
561 pub(crate) expect_continue: bool,
562 error: Option<HttpError>,
563}
564
565impl Request {
566 /// A request with `method` and `target`, validated when it is sent:
567 /// the method must be a token and the target must be in a form the
568 /// method allows (`/path?query`, an absolute URI, `host:port` for
569 /// `CONNECT`, or `*` for `OPTIONS`).
570 ///
571 /// # Examples
572 ///
573 /// ```
574 /// let r = http_net::Request::new("PURGE", "/cache/a");
575 /// assert_eq!(r.method(), "PURGE");
576 /// ```
577 #[must_use]
578 pub fn new(method: &str, target: &str) -> Self {
579 let method_kind = Method::from_bytes(method.as_bytes());
580 let mut error = None;
581 if !is_token(method.as_bytes()) {
582 error = Some(HttpError::InvalidMethod);
583 } else if let Err(e) = check_target(method_kind, target.as_bytes()) {
584 error = Some(e);
585 }
586 Self {
587 method: match method_kind.as_str() {
588 Some(s) => Cow::Borrowed(s),
589 None => Cow::Owned(method.into()),
590 },
591 method_kind,
592 target: target.into(),
593 fields: Vec::new(),
594 body: OutBody::Full(Cow::Borrowed(&[])),
595 close: false,
596 upgrade: false,
597 has_host: false,
598 expect_continue: false,
599 error,
600 }
601 }
602
603 /// A `GET` request.
604 ///
605 /// # Examples
606 ///
607 /// ```
608 /// assert_eq!(http_net::Request::get("/").method(), "GET");
609 /// ```
610 #[must_use]
611 pub fn get(target: &str) -> Self {
612 Self::new("GET", target)
613 }
614
615 /// A `POST` request.
616 ///
617 /// # Examples
618 ///
619 /// ```
620 /// assert_eq!(http_net::Request::post("/").method(), "POST");
621 /// ```
622 #[must_use]
623 pub fn post(target: &str) -> Self {
624 Self::new("POST", target)
625 }
626
627 /// A `HEAD` request: its response carries headers and no body.
628 ///
629 /// # Examples
630 ///
631 /// ```
632 /// assert_eq!(http_net::Request::head("/").method(), "HEAD");
633 /// ```
634 #[must_use]
635 pub fn head(target: &str) -> Self {
636 Self::new("HEAD", target)
637 }
638
639 /// A `PUT` request.
640 ///
641 /// # Examples
642 ///
643 /// ```
644 /// assert_eq!(http_net::Request::put("/files/a").method(), "PUT");
645 /// ```
646 #[must_use]
647 pub fn put(target: &str) -> Self {
648 Self::new("PUT", target)
649 }
650
651 /// A `DELETE` request.
652 ///
653 /// # Examples
654 ///
655 /// ```
656 /// assert_eq!(http_net::Request::delete("/files/a").method(), "DELETE");
657 /// ```
658 #[must_use]
659 pub fn delete(target: &str) -> Self {
660 Self::new("DELETE", target)
661 }
662
663 /// An `OPTIONS` request; `*` asks about the server as a whole.
664 ///
665 /// # Examples
666 ///
667 /// ```
668 /// assert_eq!(http_net::Request::options("*").target(), "*");
669 /// ```
670 #[must_use]
671 pub fn options(target: &str) -> Self {
672 Self::new("OPTIONS", target)
673 }
674
675 /// A request for an upstream server built from a received head, for a
676 /// proxy: the same method and target, every field except the hop-by-hop
677 /// ones (RFC 9110 section 7.6.1: `connection`, `keep-alive`,
678 /// `proxy-connection`, `te`, `transfer-encoding`, `upgrade`, `trailer`,
679 /// and any name the head's `Connection` lists; see
680 /// [`is_hop_by_hop`](crate::is_hop_by_hop)) in their original order,
681 /// except `content-length`, which the connection derives again from
682 /// the body mode of the head's framing: a chunked body stays
683 /// chunked, a length stays that length (stream it with
684 /// [`ClientConn::send_body`](crate::ClientConn::send_body)). The
685 /// connection writes its own framing and connection fields.
686 ///
687 /// The target is copied as received: an absolute-form target (a request
688 /// to a proxy) stays absolute, which an origin server also accepts (RFC
689 /// 9112 section 3.2.2); rewrite it with [`Request::new`] if the upstream
690 /// wants origin form. `Host` is forwarded as a field. Add `via` and
691 /// forwarding fields with [`header`](Self::header); requests are
692 /// re-serialized, never passed through as raw bytes.
693 ///
694 /// # Errors
695 ///
696 /// Recorded and returned by
697 /// [`ClientConn::send_request`](crate::ClientConn::send_request):
698 /// [`HttpError::InvalidState`] for an upgrade request (the upgrade is
699 /// hop-by-hop; a proxy tunnels it instead), and the builder errors of
700 /// [`Request::new`] and [`Request::header`] (for example a missing or
701 /// second `host` surfaces at send).
702 ///
703 /// # Examples
704 ///
705 /// ```
706 /// use http_net::{ClientConn, Request, ServerConn, ServerEvent};
707 ///
708 /// let mut downstream = ServerConn::new();
709 /// let _ = downstream.feed(b"POST /api HTTP/1.1\r\nHost: app\r\nConnection: X-Hop\r\nX-Hop: 1\r\nKeep-Alive: timeout=5\r\nAccept: */*\r\nContent-Length: 2\r\n\r\nhi");
710 /// let ServerEvent::Request(head) = downstream.poll()? else { unreachable!() };
711 /// let upstream_req = Request::forward_from(&head).header("via", "1.1 proxy");
712 ///
713 /// let mut upstream = ClientConn::new();
714 /// let mut out = Vec::new();
715 /// upstream.send_request(&upstream_req, &mut out)?;
716 /// assert_eq!(out, b"POST /api HTTP/1.1\r\nHost: app\r\nAccept: */*\r\nvia: 1.1 proxy\r\ncontent-length: 2\r\n\r\n");
717 /// # Ok::<(), http_net::HttpError>(())
718 /// ```
719 #[must_use]
720 pub fn forward_from(head: &RequestHead<'_>) -> Self {
721 let mut request = Self::new(head.method_str(), head.target());
722 if head.is_upgrade() {
723 if request.error.is_none() {
724 request.error = Some(HttpError::InvalidState(
725 "an upgrade request is hop-by-hop; tunnel it instead of forwarding it",
726 ));
727 }
728 return request;
729 }
730 for (name, value) in head.headers() {
731 // `content-length` is end-to-end, but the connection derives it
732 // from the body mode below, so it is not copied as a field.
733 if !is_hop_by_hop(name, head.connection_options())
734 && !name.eq_ignore_ascii_case("content-length")
735 {
736 request = request.header(name, value);
737 }
738 }
739 match head.framing() {
740 BodyFraming::Chunked => request.chunked(),
741 BodyFraming::Length(n) => request.content_length(n),
742 _ => request,
743 }
744 }
745
746 /// Adds a header field, validated as for [`Response::header`]. A second
747 /// `host` is refused with [`HttpError::InvalidHost`].
748 ///
749 /// # Examples
750 ///
751 /// ```
752 /// let r = http_net::Request::get("/").header("host", "a").header("accept", "*/*");
753 /// assert_eq!(r.header_value("accept"), Some(&b"*/*"[..]));
754 /// ```
755 #[must_use]
756 pub fn header(mut self, name: &str, value: impl AsRef<[u8]>) -> Self {
757 if self.error.is_none() {
758 let value = value.as_ref();
759 let is_host = name.eq_ignore_ascii_case("host");
760 if is_host && (self.has_host || !crate::scan::is_host_value(value)) {
761 self.error = Some(HttpError::InvalidHost);
762 } else if let Err(e) = push_field(&mut self.fields, name, value, REQUEST_RESERVED) {
763 self.error = Some(e);
764 } else {
765 self.has_host |= is_host;
766 }
767 }
768 self
769 }
770
771 /// Sets the whole body, sent with `content-length`.
772 ///
773 /// # Examples
774 ///
775 /// ```
776 /// let r = http_net::Request::post("/").body(&b"x=1"[..]);
777 /// assert_eq!(r.body_bytes(), Some(&b"x=1"[..]));
778 /// ```
779 #[must_use]
780 pub fn body(mut self, body: impl Into<Cow<'static, [u8]>>) -> Self {
781 self.body = OutBody::Full(body.into());
782 self
783 }
784
785 /// Streams the body with chunked encoding through
786 /// [`ClientConn::send_body`](crate::ClientConn::send_body) and
787 /// [`ClientConn::end_body`](crate::ClientConn::end_body).
788 ///
789 /// # Examples
790 ///
791 /// ```
792 /// assert_eq!(http_net::Request::post("/").chunked().body_bytes(), None);
793 /// ```
794 #[must_use]
795 pub fn chunked(mut self) -> Self {
796 self.body = OutBody::Chunked;
797 self
798 }
799
800 /// Streams a body of exactly `len` bytes.
801 ///
802 /// # Examples
803 ///
804 /// ```
805 /// assert_eq!(http_net::Request::post("/").content_length(4).body_bytes(), None);
806 /// ```
807 #[must_use]
808 pub fn content_length(mut self, len: u64) -> Self {
809 self.body = OutBody::Length(len);
810 self
811 }
812
813 /// Asks the server to close the connection after its response.
814 ///
815 /// # Examples
816 ///
817 /// ```
818 /// let _ = http_net::Request::get("/").header("host", "a").close();
819 /// ```
820 #[must_use]
821 pub fn close(mut self) -> Self {
822 self.close = true;
823 self
824 }
825
826 /// Sends `expect: 100-continue` and holds the body back until the
827 /// server agrees (RFC 9110 section 10.1.1): for large uploads the
828 /// server may refuse from the head alone. The body must be streamed
829 /// ([`content_length`](Self::content_length) or
830 /// [`chunked`](Self::chunked)); send it with
831 /// [`ClientConn::send_body`](crate::ClientConn::send_body) after
832 /// [`ClientEvent::Continue`](crate::ClientEvent::Continue), or after a
833 /// wait of your choosing. A final status instead means the server does
834 /// not want the body: it must not be sent, and the connection closes
835 /// after the response.
836 ///
837 /// # Examples
838 ///
839 /// ```
840 /// use http_net::{ClientConn, ClientEvent, HttpError, Request};
841 ///
842 /// let mut conn = ClientConn::new();
843 /// let mut out = Vec::new();
844 /// let upload = Request::put("/f").header("host", "a").content_length(9).expect_continue();
845 /// conn.send_request(&upload, &mut out)?;
846 /// assert!(out.ends_with(b"expect: 100-continue\r\ncontent-length: 9\r\n\r\n"));
847 /// let _ = conn.feed(b"HTTP/1.1 413 Content Too Large\r\nContent-Length: 0\r\n\r\n");
848 /// assert!(matches!(conn.poll()?, ClientEvent::Response(r) if r.status() == 413));
849 /// assert!(matches!(conn.send_body(b"too large", &mut out), Err(HttpError::InvalidState(_))));
850 /// assert!(matches!(conn.poll()?, ClientEvent::ResponseEnd));
851 /// assert!(matches!(conn.poll()?, ClientEvent::Closed));
852 /// # Ok::<(), http_net::HttpError>(())
853 /// ```
854 #[must_use]
855 pub fn expect_continue(mut self) -> Self {
856 if self.error.is_none() && !self.expect_continue {
857 self.expect_continue = true;
858 self.fields.extend_from_slice(b"expect: 100-continue\r\n");
859 }
860 self
861 }
862
863 /// Asks to switch the connection to `protocol` (writes
864 /// `connection: upgrade` and `upgrade: <protocol>`). A `101` answer
865 /// hands the connection over; see
866 /// [`ClientEvent::Upgraded`](crate::ClientEvent::Upgraded).
867 ///
868 /// `protocol` is a token or a `name/version` product token. `h2c` is
869 /// refused with [`HttpError::UpgradeNotOffered`]: RFC 9113 section 3.1
870 /// deprecates the HTTP/2 upgrade.
871 ///
872 /// # Examples
873 ///
874 /// ```
875 /// use http_net::{ClientConn, HttpError, Request};
876 ///
877 /// let r = Request::get("/rpc").header("host", "a").upgrade("websocket");
878 /// assert_eq!(r.header_value("upgrade"), Some(&b"websocket"[..]));
879 ///
880 /// let h2c = Request::get("/").header("host", "a").upgrade("h2c");
881 /// assert_eq!(
882 /// ClientConn::new().send_request(&h2c, &mut Vec::new()),
883 /// Err(HttpError::UpgradeNotOffered)
884 /// );
885 /// ```
886 #[must_use]
887 pub fn upgrade(mut self, protocol: &str) -> Self {
888 if self.error.is_none() {
889 if is_h2c(protocol.as_bytes()) {
890 self.error = Some(HttpError::UpgradeNotOffered);
891 } else if is_protocol(protocol.as_bytes()) {
892 self.upgrade = true;
893 self.fields
894 .extend_from_slice(b"connection: upgrade\r\nupgrade: ");
895 self.fields.extend_from_slice(protocol.as_bytes());
896 self.fields.extend_from_slice(b"\r\n");
897 } else {
898 self.error = Some(HttpError::InvalidHeaderValue);
899 }
900 }
901 self
902 }
903
904 /// The method text.
905 ///
906 /// # Examples
907 ///
908 /// ```
909 /// assert_eq!(http_net::Request::new("OPTIONS", "*").method(), "OPTIONS");
910 /// ```
911 #[must_use]
912 pub fn method(&self) -> &str {
913 &self.method
914 }
915
916 /// The request target.
917 ///
918 /// # Examples
919 ///
920 /// ```
921 /// assert_eq!(http_net::Request::get("/a?b").target(), "/a?b");
922 /// ```
923 #[must_use]
924 pub fn target(&self) -> &str {
925 &self.target
926 }
927
928 /// The full body, or `None` for a streamed one.
929 ///
930 /// # Examples
931 ///
932 /// ```
933 /// assert_eq!(http_net::Request::get("/").body_bytes(), Some(&[][..]));
934 /// ```
935 #[must_use]
936 pub fn body_bytes(&self) -> Option<&[u8]> {
937 match &self.body {
938 OutBody::Full(b) => Some(b),
939 _ => None,
940 }
941 }
942
943 /// The value of the first header named `name` added to this request.
944 ///
945 /// # Examples
946 ///
947 /// ```
948 /// let r = http_net::Request::get("/").header("host", "a");
949 /// assert_eq!(r.header_value("HOST"), Some(&b"a"[..]));
950 /// ```
951 #[must_use]
952 pub fn header_value(&self, name: &str) -> Option<&[u8]> {
953 field_value(&self.fields, name)
954 }
955
956 pub(crate) fn error(&self) -> Option<HttpError> {
957 self.error
958 }
959
960 pub(crate) fn fields(&self) -> &[u8] {
961 &self.fields
962 }
963
964 /// The values of the `upgrade` fields [`upgrade`](Self::upgrade) added
965 /// (`header` refuses the name, so these are the only ones).
966 pub(crate) fn upgrade_offers(&self) -> impl Iterator<Item = &[u8]> {
967 serialized_fields(&self.fields)
968 .filter(|(n, _)| n.eq_ignore_ascii_case(b"upgrade"))
969 .map(|(_, v)| v)
970 }
971}
972
973/// Shows method, target, fields, and body size; the values of credential
974/// fields (`authorization`, `proxy-authorization`, `cookie`) are replaced by
975/// `<redacted N bytes>`.
976impl fmt::Debug for Request {
977 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
978 f.debug_struct("Request")
979 .field("method", &self.method)
980 .field("target", &self.target)
981 .field("fields", &SerializedFields(&self.fields))
982 .field("body", &body_debug(&self.body))
983 .field("error", &self.error)
984 .finish()
985 }
986}
987
988fn body_debug(body: &OutBody) -> String {
989 match body {
990 OutBody::Full(b) => format!("{} bytes", b.len()),
991 OutBody::Length(n) => format!("streamed, {n} bytes"),
992 OutBody::Chunked => "streamed, chunked".into(),
993 }
994}
995
996/// Looks a header up in serialized `name: value\r\n` lines.
997fn field_value<'a>(fields: &'a [u8], name: &str) -> Option<&'a [u8]> {
998 serialized_fields(fields)
999 .find(|(n, _)| n.eq_ignore_ascii_case(name.as_bytes()))
1000 .map(|(_, v)| v)
1001}
1002
1003#[cfg(test)]
1004mod tests {
1005 use alloc::string::ToString;
1006
1007 use super::*;
1008
1009 #[test]
1010 fn test_response_refuses_reserved_and_invalid_headers() {
1011 assert_eq!(
1012 Response::new(200)
1013 .header("Transfer-Encoding", "chunked")
1014 .error(),
1015 Some(HttpError::ReservedHeader)
1016 );
1017 assert_eq!(
1018 Response::new(200).header("bad name", "x").error(),
1019 Some(HttpError::InvalidHeaderName)
1020 );
1021 assert_eq!(
1022 Response::new(200).header("x", "a\r\nInjected: 1").error(),
1023 Some(HttpError::BareCr)
1024 );
1025 assert_eq!(
1026 Response::new(200).header("x", " padded").error(),
1027 Some(HttpError::InvalidHeaderValue)
1028 );
1029 // The first error sticks.
1030 let r = Response::new(200)
1031 .header("a b", "1")
1032 .header("connection", "close");
1033 assert_eq!(r.error(), Some(HttpError::InvalidHeaderName));
1034 }
1035
1036 #[test]
1037 fn test_request_validates_method_target_and_host() {
1038 assert_eq!(
1039 Request::new("G T", "/").error(),
1040 Some(HttpError::InvalidMethod)
1041 );
1042 assert_eq!(
1043 Request::get("no-slash").error(),
1044 Some(HttpError::InvalidTarget)
1045 );
1046 assert_eq!(
1047 Request::get("/")
1048 .header("host", "a")
1049 .header("Host", "b")
1050 .error(),
1051 Some(HttpError::InvalidHost)
1052 );
1053 assert_eq!(
1054 Request::get("/").header("host", "a b").error(),
1055 Some(HttpError::InvalidHost)
1056 );
1057 assert!(Request::get("/").header("host", "a").has_host);
1058 assert_eq!(
1059 Request::get("/").upgrade("a b").error(),
1060 Some(HttpError::InvalidHeaderValue)
1061 );
1062 }
1063
1064 #[test]
1065 fn test_from_error_uses_status_text_and_close() {
1066 let r = Response::from_error(&HttpError::UnsupportedTransferEncoding);
1067 assert_eq!(r.status(), 501);
1068 assert!(r.closes());
1069 assert_eq!(
1070 r.header_value("content-type"),
1071 Some(&b"text/plain; charset=utf-8"[..])
1072 );
1073 assert_eq!(
1074 r.body_bytes(),
1075 Some(
1076 HttpError::UnsupportedTransferEncoding
1077 .to_string()
1078 .as_bytes()
1079 )
1080 );
1081 // Errors without a request status still get a well-formed 400.
1082 assert_eq!(
1083 Response::from_error(&HttpError::UnexpectedEof).status(),
1084 400
1085 );
1086 assert_eq!(
1087 Response::from_error(&HttpError::InvalidState("x")).error(),
1088 None
1089 );
1090 }
1091
1092 #[test]
1093 fn test_request_shortcuts_set_the_method() {
1094 for (r, m) in [
1095 (Request::head("/"), "HEAD"),
1096 (Request::put("/"), "PUT"),
1097 (Request::delete("/"), "DELETE"),
1098 (Request::options("*"), "OPTIONS"),
1099 ] {
1100 assert_eq!(r.method(), m);
1101 assert_eq!(r.error(), None);
1102 }
1103 assert_eq!(Request::put("*").error(), Some(HttpError::InvalidTarget));
1104 assert_eq!(Request::head("/").method_kind, Method::Head);
1105 }
1106
1107 #[test]
1108 fn test_upgrade_builders_accept_product_tokens_and_refuse_h2c() {
1109 let r = Response::switching_protocols("foo/2");
1110 assert_eq!(r.error(), None);
1111 assert_eq!(r.upgrade_protocol(), Some(&b"foo/2"[..]));
1112 assert_eq!(
1113 Response::switching_protocols("h2c").error(),
1114 Some(HttpError::UpgradeNotOffered)
1115 );
1116 assert_eq!(
1117 Response::switching_protocols("H2C").error(),
1118 Some(HttpError::UpgradeNotOffered)
1119 );
1120 assert_eq!(
1121 Response::switching_protocols("foo/").error(),
1122 Some(HttpError::InvalidHeaderValue)
1123 );
1124 assert_eq!(Response::new(200).upgrade_protocol(), None);
1125 assert_eq!(
1126 Request::get("/").upgrade("h2c").error(),
1127 Some(HttpError::UpgradeNotOffered)
1128 );
1129 assert!(Request::get("/").upgrade("websocket/13").upgrade);
1130 }
1131
1132 /// Threat T-35: credential values never reach `Debug` output.
1133 #[test]
1134 fn test_debug_redacts_credential_fields() {
1135 let r = Response::new(200)
1136 .header("Set-Cookie", "sid=s3cr3t; HttpOnly")
1137 .header("x-trace", "visible");
1138 let shown = alloc::format!("{r:?}");
1139 assert!(!shown.contains("s3cr3t"), "{shown}");
1140 assert!(shown.contains("<redacted 20 bytes>"), "{shown}");
1141 assert!(shown.contains("visible"), "{shown}");
1142 assert_eq!(
1143 r.header_value("set-cookie"),
1144 Some(&b"sid=s3cr3t; HttpOnly"[..])
1145 );
1146
1147 let q = Request::get("/")
1148 .header("host", "a")
1149 .header("Authorization", "Bearer t0k3n")
1150 .header("PROXY-AUTHORIZATION", "Basic cDpx")
1151 .header("cookie", "a=b");
1152 let shown = alloc::format!("{q:?}");
1153 for secret in ["t0k3n", "cDpx", "a=b"] {
1154 assert!(!shown.contains(secret), "{shown}");
1155 }
1156 assert!(shown.contains("\"host\", \"a\""), "{shown}");
1157 }
1158
1159 #[test]
1160 fn test_field_lookup_and_debug() {
1161 let r = Response::switching_protocols("websocket").header("x", "");
1162 assert_eq!(r.header_value("connection"), Some(&b"upgrade"[..]));
1163 assert_eq!(r.header_value("x"), Some(&b""[..]));
1164 assert!(alloc::format!("{r:?}").contains("Response"));
1165 assert_eq!(
1166 Response::switching_protocols("a b").error(),
1167 Some(HttpError::InvalidHeaderValue)
1168 );
1169 }
1170}