Skip to main content

http_extract/
header.rs

1//! Strict building blocks for HTTP field maps.
2//!
3//! These helpers implement the field-line handling used by the higher-level
4//! extractors. They deliberately do not combine repeated lines unless the
5//! field-specific extractor owns that grammar. See [RFC 9110, Section 5]
6//! for HTTP field semantics.
7//!
8//! [RFC 9110, Section 5]: https://www.rfc-editor.org/rfc/rfc9110.html#section-5
9
10use http::{HeaderMap, HeaderName, HeaderValue};
11
12use crate::Error;
13
14/// Append one already validated field value without replacing existing values.
15///
16/// Existing values for `name` remain in the map and `value` is appended after
17/// them. The return value is `true` when the map already contained at least one
18/// value for `name`, and `false` when this call inserted its first value.
19/// [`HeaderName`] and [`HeaderValue`] validate their own inputs before this
20/// function is called, so appending is infallible. The helper does not inspect,
21/// format, or log the value; callers must continue to treat credential-bearing
22/// values as sensitive.
23pub fn append_header_value(headers: &mut HeaderMap, name: HeaderName, value: HeaderValue) -> bool {
24    headers.append(name, value)
25}
26
27/// Extract a field value only when the field has at most one field line.
28///
29/// A missing field returns `None`. More than one field line returns
30/// [`Error::DuplicateHeader`]. The returned [`HeaderValue`] is otherwise raw:
31/// this function performs no text decoding, syntax validation, authentication,
32/// or logging. It does not combine repeated lines or split values on commas;
33/// those operations are only valid for fields whose own grammar permits them.
34pub fn extract_single_header_value<'a>(
35    headers: &'a HeaderMap,
36    name: &HeaderName,
37) -> Result<Option<&'a HeaderValue>, Error> {
38    let mut values = headers.get_all(name).iter();
39    let first = values.next();
40    if values.next().is_some() {
41        return Err(Error::duplicate_header(name.clone()));
42    }
43    Ok(first)
44}
45
46/// Extract a singular field as text without silently discarding invalid bytes.
47///
48/// A missing field returns `None`, duplicate field lines return
49/// [`Error::DuplicateHeader`], and a value that cannot be represented as text
50/// returns [`Error::InvalidHeader`]. Empty text is preserved as `Some("")`.
51/// No field-specific syntax validation is performed. Returned text may contain
52/// credentials or other sensitive data and must not be logged or echoed; errors
53/// identify only the field name and error category, never the value.
54pub fn extract_single_header_text<'a>(
55    headers: &'a HeaderMap,
56    name: &HeaderName,
57) -> Result<Option<&'a str>, Error> {
58    extract_single_header_value(headers, name)?
59        .map(|value| {
60            value
61                .to_str()
62                .map_err(|_| Error::invalid_header(name.clone()))
63        })
64        .transpose()
65}
66
67#[cfg(test)]
68mod tests {
69    use http::{HeaderMap, HeaderValue, header::USER_AGENT};
70
71    use super::*;
72
73    #[test]
74    fn distinguishes_missing_invalid_and_duplicate() {
75        let mut headers = HeaderMap::new();
76        assert_eq!(extract_single_header_text(&headers, &USER_AGENT), Ok(None));
77
78        headers.insert(USER_AGENT, HeaderValue::from_bytes(&[0xff]).unwrap());
79        assert!(matches!(
80            extract_single_header_text(&headers, &USER_AGENT),
81            Err(Error::InvalidHeader { .. })
82        ));
83
84        headers.clear();
85        headers.append(USER_AGENT, HeaderValue::from_static("one"));
86        headers.append(USER_AGENT, HeaderValue::from_static("two"));
87        assert!(matches!(
88            extract_single_header_text(&headers, &USER_AGENT),
89            Err(Error::DuplicateHeader { .. })
90        ));
91    }
92
93    #[test]
94    fn append_preserves_existing_values() {
95        let mut headers = HeaderMap::new();
96        let name = HeaderName::from_static("x-example");
97        assert!(!append_header_value(
98            &mut headers,
99            name.clone(),
100            HeaderValue::from_static("first"),
101        ));
102        assert!(append_header_value(
103            &mut headers,
104            name.clone(),
105            HeaderValue::from_static("second"),
106        ));
107        assert_eq!(
108            headers
109                .get_all(name)
110                .iter()
111                .map(HeaderValue::to_str)
112                .collect::<Result<Vec<_>, _>>()
113                .unwrap(),
114            vec!["first", "second"]
115        );
116    }
117}