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