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