Skip to main content

cloud_sdk/transport/header/
request.rs

1use core::fmt;
2
3use super::{
4    ContentType, HeaderError, HeaderName, HeaderSensitivity, HeaderValue, MAX_REQUEST_HEADER_BYTES,
5    MAX_REQUEST_HEADERS, MediaType, encoded_line_len, is_reserved_request_name, typed_accept,
6    typed_content_type,
7};
8
9/// One validated borrowed request header.
10///
11/// Ordinary equality is intentionally unavailable because the value may be
12/// sensitive.
13///
14/// ```compile_fail
15/// use cloud_sdk::transport::RequestHeader;
16///
17/// let left = RequestHeader::sensitive("x-secret", "secret").unwrap();
18/// let right = RequestHeader::sensitive("x-secret", "secret").unwrap();
19/// let _ = left == right;
20/// ```
21#[derive(Clone, Copy)]
22pub struct RequestHeader<'a> {
23    name: HeaderName<'a>,
24    value: HeaderValue<'a>,
25    sensitivity: HeaderSensitivity,
26}
27
28impl<'a> RequestHeader<'a> {
29    /// Creates a public request header.
30    pub fn new(name: &'a str, value: &'a str) -> Result<Self, HeaderError> {
31        Self::from_parts(
32            HeaderName::new(name)?,
33            HeaderValue::new(value)?,
34            HeaderSensitivity::Public,
35        )
36    }
37
38    /// Creates a sensitive request header.
39    pub fn sensitive(name: &'a str, value: &'a str) -> Result<Self, HeaderError> {
40        Self::from_parts(
41            HeaderName::new(name)?,
42            HeaderValue::new(value)?,
43            HeaderSensitivity::Sensitive,
44        )
45    }
46
47    /// Creates a typed `Accept` header.
48    #[must_use]
49    pub const fn accept(media_type: MediaType<'a>) -> Self {
50        Self {
51            name: HeaderName("accept"),
52            value: typed_accept(media_type),
53            sensitivity: HeaderSensitivity::Public,
54        }
55    }
56
57    /// Creates a typed `Content-Type` header.
58    #[must_use]
59    pub const fn content_type(content_type: ContentType<'a>) -> Self {
60        Self {
61            name: HeaderName("content-type"),
62            value: typed_content_type(content_type),
63            sensitivity: HeaderSensitivity::Public,
64        }
65    }
66
67    /// Returns the exact header name.
68    #[must_use]
69    pub const fn name(self) -> HeaderName<'a> {
70        self.name
71    }
72
73    /// Returns the exact header value.
74    #[must_use]
75    pub const fn value(self) -> HeaderValue<'a> {
76        self.value
77    }
78
79    /// Returns the value sensitivity.
80    #[must_use]
81    pub const fn sensitivity(self) -> HeaderSensitivity {
82        self.sensitivity
83    }
84
85    fn from_parts(
86        name: HeaderName<'a>,
87        value: HeaderValue<'a>,
88        sensitivity: HeaderSensitivity,
89    ) -> Result<Self, HeaderError> {
90        if is_reserved_request_name(name) {
91            return Err(HeaderError::ReservedRequestHeader);
92        }
93        if name.eq_ignore_ascii_case("content-type") && ContentType::new(value.as_str()).is_err() {
94            return Err(HeaderError::InvalidContentType);
95        }
96        Ok(Self {
97            name,
98            value,
99            sensitivity,
100        })
101    }
102}
103
104impl fmt::Debug for RequestHeader<'_> {
105    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
106        formatter
107            .debug_struct("RequestHeader")
108            .field("name", &self.name)
109            .field("value", &"[redacted]")
110            .field("sensitivity", &self.sensitivity)
111            .finish()
112    }
113}
114
115/// Validated ordered request-header block.
116#[derive(Clone, Copy)]
117pub struct RequestHeaders<'a> {
118    entries: &'a [RequestHeader<'a>],
119    encoded_len: usize,
120}
121
122impl RequestHeaders<'static> {
123    /// Empty request-header block.
124    pub const EMPTY: Self = Self {
125        entries: &[],
126        encoded_len: 0,
127    };
128}
129
130impl<'a> RequestHeaders<'a> {
131    /// Validates count, aggregate size, reserved ownership, and duplicates.
132    pub fn new(entries: &'a [RequestHeader<'a>]) -> Result<Self, HeaderError> {
133        if entries.len() > MAX_REQUEST_HEADERS {
134            return Err(HeaderError::TooManyHeaders);
135        }
136        let mut encoded_len = 0_usize;
137        for (index, entry) in entries.iter().enumerate() {
138            if is_reserved_request_name(entry.name) {
139                return Err(HeaderError::ReservedRequestHeader);
140            }
141            if entries.get(..index).is_some_and(|seen| {
142                seen.iter()
143                    .any(|candidate| candidate.name.eq_ignore_ascii_case(entry.name.as_str()))
144            }) {
145                return Err(HeaderError::DuplicateName);
146            }
147            let line_len = encoded_line_len(entry.name.as_str().len(), entry.value.as_str().len())?;
148            encoded_len = encoded_len
149                .checked_add(line_len)
150                .ok_or(HeaderError::AggregateTooLarge)?;
151            if encoded_len > MAX_REQUEST_HEADER_BYTES {
152                return Err(HeaderError::AggregateTooLarge);
153            }
154        }
155        Ok(Self {
156            entries,
157            encoded_len,
158        })
159    }
160
161    /// Returns the ordered entries.
162    #[must_use]
163    pub const fn as_slice(self) -> &'a [RequestHeader<'a>] {
164        self.entries
165    }
166
167    /// Returns the encoded HTTP/1 field-line length without a final empty line.
168    #[must_use]
169    pub const fn encoded_len(self) -> usize {
170        self.encoded_len
171    }
172
173    /// Finds a header using ASCII case-insensitive name comparison.
174    #[must_use]
175    pub fn get(self, name: &str) -> Option<RequestHeader<'a>> {
176        self.entries
177            .iter()
178            .copied()
179            .find(|entry| entry.name.eq_ignore_ascii_case(name))
180    }
181
182    /// Atomically writes all field lines as `name: value\r\n`.
183    ///
184    /// The output is unchanged when it is too small.
185    pub fn encode_http1(self, output: &mut [u8]) -> Result<usize, HeaderError> {
186        if output.len() < self.encoded_len {
187            return Err(HeaderError::OutputTooSmall);
188        }
189        let target = output
190            .get_mut(..self.encoded_len)
191            .ok_or(HeaderError::OutputTooSmall)?;
192        let mut offset = 0_usize;
193        for entry in self.entries {
194            offset = write_line(target, offset, *entry)?;
195        }
196        Ok(offset)
197    }
198}
199
200impl fmt::Debug for RequestHeaders<'_> {
201    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
202        formatter
203            .debug_struct("RequestHeaders")
204            .field("count", &self.entries.len())
205            .field("encoded_len", &self.encoded_len)
206            .field("values", &"[redacted]")
207            .finish()
208    }
209}
210
211fn write_line(
212    output: &mut [u8],
213    offset: usize,
214    header: RequestHeader<'_>,
215) -> Result<usize, HeaderError> {
216    let name = header.name.as_str().as_bytes();
217    let value = header.value.as_str().as_bytes();
218    let line_len = encoded_line_len(name.len(), value.len())?;
219    let end = offset
220        .checked_add(line_len)
221        .ok_or(HeaderError::OutputTooSmall)?;
222    let line = output
223        .get_mut(offset..end)
224        .ok_or(HeaderError::OutputTooSmall)?;
225    let (name_out, rest) = line.split_at_mut(name.len());
226    let (separator, rest) = rest.split_at_mut(2);
227    let (value_out, ending) = rest.split_at_mut(value.len());
228    name_out.copy_from_slice(name);
229    separator.copy_from_slice(b": ");
230    value_out.copy_from_slice(value);
231    ending.copy_from_slice(b"\r\n");
232    Ok(end)
233}