Skip to main content

http_extract/
authority.rs

1//! Request authority and `Host` field extraction.
2//!
3//! The request-target authority and the `Host` field are defined by
4//! [RFC 9110, Section 7.2]. This module returns an already parsed
5//! [`http::uri::Authority`] and does not perform DNS resolution, origin
6//! authorization, or proxy trust decisions.
7//!
8//! [RFC 9110, Section 7.2]: https://www.rfc-editor.org/rfc/rfc9110.html#section-7.2
9
10use http::{HeaderMap, Request, header::HOST, uri::Authority};
11
12use crate::{Error, header::extract_single_header_text};
13
14/// Extract a strict, singular, syntactically valid `Host` authority.
15///
16/// A missing field returns `None`. Duplicate, non-text, and syntactically
17/// invalid fields, including an empty value, return an error that does not
18/// contain the field value. This function reads only the [`HeaderMap`]; it does
19/// not inspect a request URI or determine whether the authority is trusted.
20pub fn extract_header_authority(headers: &HeaderMap) -> Result<Option<Authority>, Error> {
21    extract_single_header_text(headers, &HOST)?
22        .map(|value| {
23            value
24                .parse::<Authority>()
25                .map_err(|_| Error::invalid_header(HOST))
26        })
27        .transpose()
28}
29
30/// Extract the authority from a complete request.
31///
32/// The already parsed URI authority takes precedence and is returned without
33/// inspecting or validating `Host`. [`extract_header_authority`] is called only
34/// when the URI has no authority, so its missing and error behavior applies only
35/// to that fallback. If neither source is present, this function returns
36/// `None`. The result is syntactically parsed but is not DNS-resolved or
37/// authorized as an origin.
38pub fn extract_request_authority<B>(request: &Request<B>) -> Result<Option<Authority>, Error> {
39    if let Some(authority) = request.uri().authority() {
40        return Ok(Some(authority.clone()));
41    }
42    extract_header_authority(request.headers())
43}
44
45#[cfg(test)]
46mod tests {
47    use http::{HeaderMap, HeaderValue, Request, header::HOST};
48
49    use super::*;
50
51    #[test]
52    fn extracts_host_authority_only() {
53        let mut headers = HeaderMap::new();
54        assert_eq!(extract_header_authority(&headers).unwrap(), None);
55
56        headers.insert(HOST, "example.com:8443".parse().unwrap());
57        assert_eq!(
58            extract_header_authority(&headers)
59                .unwrap()
60                .unwrap()
61                .as_str(),
62            "example.com:8443"
63        );
64    }
65
66    #[test]
67    fn host_authority_rejects_invalid_duplicate_and_non_text_fields() {
68        let mut invalid = HeaderMap::new();
69        invalid.insert(HOST, "not a valid authority".parse().unwrap());
70        assert!(matches!(
71            extract_header_authority(&invalid),
72            Err(Error::InvalidHeader { .. })
73        ));
74
75        let mut duplicate = HeaderMap::new();
76        duplicate.append(HOST, "one.example".parse().unwrap());
77        duplicate.append(HOST, "two.example".parse().unwrap());
78        assert!(matches!(
79            extract_header_authority(&duplicate),
80            Err(Error::DuplicateHeader { .. })
81        ));
82
83        let mut non_text = HeaderMap::new();
84        non_text.insert(HOST, HeaderValue::from_bytes(&[0xff]).unwrap());
85        assert!(matches!(
86            extract_header_authority(&non_text),
87            Err(Error::InvalidHeader { .. })
88        ));
89    }
90
91    #[test]
92    fn request_uri_authority_ignores_invalid_host() {
93        let request = Request::builder()
94            .uri("https://example.com/items")
95            .header(HOST, "not a valid authority")
96            .body(())
97            .unwrap();
98        assert_eq!(
99            extract_request_authority(&request)
100                .unwrap()
101                .unwrap()
102                .as_str(),
103            "example.com"
104        );
105    }
106
107    #[test]
108    fn request_uri_authority_ignores_duplicate_host() {
109        let mut request = Request::builder()
110            .uri("https://example.com/items")
111            .body(())
112            .unwrap();
113        request
114            .headers_mut()
115            .append(HOST, "one.example".parse().unwrap());
116        request
117            .headers_mut()
118            .append(HOST, "two.example".parse().unwrap());
119
120        assert_eq!(
121            extract_request_authority(&request)
122                .unwrap()
123                .unwrap()
124                .as_str(),
125            "example.com"
126        );
127    }
128
129    #[test]
130    fn request_falls_back_to_host_without_uri_authority() {
131        let request = Request::builder()
132            .uri("/items")
133            .header(HOST, "fallback.example:8443")
134            .body(())
135            .unwrap();
136
137        assert_eq!(
138            extract_request_authority(&request)
139                .unwrap()
140                .unwrap()
141                .as_str(),
142            "fallback.example:8443"
143        );
144    }
145
146    #[test]
147    fn request_without_uri_or_host_authority_returns_none() {
148        let request = Request::builder().uri("/items").body(()).unwrap();
149
150        assert_eq!(extract_request_authority(&request).unwrap(), None);
151    }
152}