Skip to main content

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    syntax::{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::syntax::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}