viaduct/headers.rs
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4pub use name::{HeaderName, InvalidHeaderName};
5use std::collections::HashMap;
6use std::iter::FromIterator;
7use std::str::FromStr;
8mod name;
9
10/// A single header. Headers have a name (case insensitive) and a value. The
11/// character set for header and values are both restrictive.
12/// - Names must only contain a-zA-Z0-9 and and ('!' | '#' | '$' | '%' | '&' |
13/// '\'' | '*' | '+' | '-' | '.' | '^' | '_' | '`' | '|' | '~') characters
14/// (the field-name token production defined at
15/// https://tools.ietf.org/html/rfc7230#section-3.2).
16/// For request headers, we expect these to all be specified statically,
17/// and so we panic if you provide an invalid one. (For response headers, we
18/// ignore headers with invalid names, but emit a warning).
19///
20/// Header names are case insensitive, and we have several pre-defined ones in
21/// the [`header_names`] module.
22///
23/// - Values may only contain printable ascii characters, and may not contain
24/// \r or \n. Strictly speaking, HTTP is more flexible for header values,
25/// however we don't need to support binary header values, and so we do not.
26///
27/// Note that typically you should not interact with this directly, and instead
28/// use the methods on [`Request`] or [`Headers`] to manipulate these.
29#[derive(Clone, Debug, PartialEq, PartialOrd, Hash, Eq, Ord)]
30pub struct Header {
31 pub name: HeaderName,
32 pub value: String,
33}
34
35// Trim `s` without copying if it can be avoided.
36fn trim_string<S: AsRef<str> + Into<String>>(s: S) -> String {
37 let sr = s.as_ref();
38 let trimmed = sr.trim();
39 if sr.len() != trimmed.len() {
40 trimmed.into()
41 } else {
42 s.into()
43 }
44}
45
46fn is_valid_header_value(value: &str) -> bool {
47 value.bytes().all(|b| (32..127).contains(&b) || b == b'\t')
48}
49
50impl Header {
51 pub fn new<Name, Value>(name: Name, value: Value) -> Result<Self, crate::ViaductError>
52 where
53 Name: Into<HeaderName>,
54 Value: AsRef<str> + Into<String>,
55 {
56 let name = name.into();
57 let value = trim_string(value);
58 if !is_valid_header_value(&value) {
59 return Err(crate::ViaductError::RequestHeaderError(name.to_string()));
60 }
61 Ok(Self { name, value })
62 }
63
64 pub fn new_unchecked<Value>(name: HeaderName, value: Value) -> Self
65 where
66 Value: AsRef<str> + Into<String>,
67 {
68 Self {
69 name,
70 value: value.into(),
71 }
72 }
73
74 #[inline]
75 pub fn name(&self) -> &HeaderName {
76 &self.name
77 }
78
79 #[inline]
80 pub fn value(&self) -> &str {
81 &self.value
82 }
83
84 #[inline]
85 fn set_value<V: AsRef<str>>(&mut self, s: V) -> Result<(), crate::ViaductError> {
86 let value = s.as_ref();
87 if !is_valid_header_value(value) {
88 Err(crate::ViaductError::RequestHeaderError(
89 self.name.to_string(),
90 ))
91 } else {
92 self.value.clear();
93 self.value.push_str(s.as_ref().trim());
94 Ok(())
95 }
96 }
97}
98
99impl std::fmt::Display for Header {
100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101 write!(f, "{}: {}", self.name, self.value)
102 }
103}
104
105/// A list of headers.
106#[derive(Clone, Debug, PartialEq, Eq, Default)]
107pub struct Headers {
108 headers: Vec<Header>,
109}
110
111impl Headers {
112 /// Initialize an empty list of headers.
113 #[inline]
114 pub fn new() -> Self {
115 Default::default()
116 }
117
118 /// Initialize an empty list of headers backed by a vector with the provided
119 /// capacity.
120 pub fn with_capacity(c: usize) -> Self {
121 Self {
122 headers: Vec::with_capacity(c),
123 }
124 }
125
126 /// Convert this list of headers to a Vec<Header>
127 #[inline]
128 pub fn into_vec(self) -> Vec<Header> {
129 self.headers
130 }
131
132 /// Returns the number of headers.
133 #[inline]
134 pub fn len(&self) -> usize {
135 self.headers.len()
136 }
137
138 /// Returns true if `len()` is zero.
139 #[inline]
140 pub fn is_empty(&self) -> bool {
141 self.headers.is_empty()
142 }
143 /// Clear this set of headers.
144 #[inline]
145 pub fn clear(&mut self) {
146 self.headers.clear();
147 }
148
149 /// Insert or update a new header.
150 ///
151 /// This returns an error if you attempt to specify a header with an
152 /// invalid value (values must be printable ASCII and may not contain
153 /// \r or \n)
154 ///
155 /// ## Example
156 /// ```
157 /// # use viaduct::Headers;
158 /// # fn main() -> Result<(), viaduct::ViaductError> {
159 /// let mut h = Headers::new();
160 /// h.insert("My-Cool-Header", "example")?;
161 /// assert_eq!(h.get("My-Cool-Header"), Some("example"));
162 ///
163 /// // Note: names are sensitive
164 /// assert_eq!(h.get("my-cool-header"), Some("example"));
165 ///
166 /// // Also note, constants for headers are in `viaduct::header_names`, and
167 /// // you can chain the result of this function.
168 /// h.insert(viaduct::header_names::CONTENT_TYPE, "something...")?
169 /// .insert("Something-Else", "etc")?;
170 /// # Ok(())
171 /// # }
172 /// ```
173 pub fn insert<N, V>(&mut self, name: N, value: V) -> Result<&mut Self, crate::ViaductError>
174 where
175 N: Into<HeaderName> + PartialEq<HeaderName>,
176 V: Into<String> + AsRef<str>,
177 {
178 if let Some(entry) = self.headers.iter_mut().find(|h| name == h.name) {
179 entry.set_value(value)?;
180 } else {
181 self.headers.push(Header::new(name, value)?);
182 }
183 Ok(self)
184 }
185
186 /// Insert the provided header unless a header is already specified.
187 /// Mostly used internally, e.g. to set "Content-Type: application/json"
188 /// in `Request::json()` unless it has been set specifically.
189 pub fn insert_if_missing<N, V>(
190 &mut self,
191 name: N,
192 value: V,
193 ) -> Result<&mut Self, crate::ViaductError>
194 where
195 N: Into<HeaderName> + PartialEq<HeaderName>,
196 V: Into<String> + AsRef<str>,
197 {
198 if !self.headers.iter_mut().any(|h| name == h.name) {
199 self.headers.push(Header::new(name, value)?);
200 }
201 Ok(self)
202 }
203
204 /// Insert or update a header directly. Typically you will want to use
205 /// `insert` over this, as it performs less work if the header needs
206 /// updating instead of insertion.
207 pub fn insert_header(&mut self, new: Header) -> &mut Self {
208 if let Some(entry) = self.headers.iter_mut().find(|h| h.name == new.name) {
209 entry.value = new.value;
210 } else {
211 self.headers.push(new);
212 }
213 self
214 }
215
216 /// Add all the headers in the provided iterator to this list of headers.
217 pub fn extend<I>(&mut self, iter: I) -> &mut Self
218 where
219 I: IntoIterator<Item = Header>,
220 {
221 let it = iter.into_iter();
222 self.headers.reserve(it.size_hint().0);
223 for h in it {
224 self.insert_header(h);
225 }
226 self
227 }
228
229 /// Add all the headers in the provided iterator, unless any of them are Err.
230 pub fn try_extend<I, E>(&mut self, iter: I) -> Result<&mut Self, E>
231 where
232 I: IntoIterator<Item = Result<Header, E>>,
233 {
234 // Not the most efficient but avoids leaving us in an unspecified state
235 // if one returns Err.
236 self.extend(iter.into_iter().collect::<Result<Vec<_>, E>>()?);
237 Ok(self)
238 }
239
240 /// Get the header object with the requested name. Usually, you will
241 /// want to use `get()` or `get_as::<T>()` instead.
242 pub fn get_header<S>(&self, name: S) -> Option<&Header>
243 where
244 S: PartialEq<HeaderName>,
245 {
246 self.headers.iter().find(|h| name == h.name)
247 }
248
249 /// Get the value of the header with the provided name.
250 ///
251 /// See also `get_as`.
252 ///
253 /// ## Example
254 /// ```
255 /// # use viaduct::{Headers, header_names::CONTENT_TYPE};
256 /// # fn main() -> Result<(), viaduct::ViaductError> {
257 /// let mut h = Headers::new();
258 /// h.insert(CONTENT_TYPE, "application/json")?;
259 /// assert_eq!(h.get(CONTENT_TYPE), Some("application/json"));
260 /// assert_eq!(h.get("Something-Else"), None);
261 /// # Ok(())
262 /// # }
263 /// ```
264 pub fn get<S>(&self, name: S) -> Option<&str>
265 where
266 S: PartialEq<HeaderName>,
267 {
268 self.get_header(name).map(|h| h.value.as_str())
269 }
270
271 /// Get the value of the header with the provided name, and
272 /// attempt to parse it using [`std::str::FromStr`].
273 ///
274 /// - If the header is missing, it returns None.
275 /// - If the header is present but parsing failed, returns
276 /// `Some(Err(<error returned by parsing>))`.
277 /// - Otherwise, returns `Some(Ok(result))`.
278 ///
279 /// Note that if `Option<Result<T, E>>` is inconvenient for you,
280 /// and you wish this returned `Result<Option<T>, E>`, you may use
281 /// the built-in `transpose()` method to convert between them.
282 ///
283 /// ```
284 /// # use viaduct::Headers;
285 /// # fn main() -> Result<(), viaduct::ViaductError> {
286 /// let mut h = Headers::new();
287 /// h.insert("Example", "1234")?.insert("Illegal", "abcd")?;
288 /// let v: Option<Result<i64, _>> = h.get_as("Example");
289 /// assert_eq!(v, Some(Ok(1234)));
290 /// assert_eq!(h.get_as::<i64, _>("Example"), Some(Ok(1234)));
291 /// assert_eq!(h.get_as::<i64, _>("Illegal"), Some("abcd".parse::<i64>()));
292 /// assert_eq!(h.get_as::<i64, _>("Something-Else"), None);
293 /// # Ok(())
294 /// # }
295 /// ```
296 pub fn get_as<T, S>(&self, name: S) -> Option<Result<T, <T as FromStr>::Err>>
297 where
298 T: FromStr,
299 S: PartialEq<HeaderName>,
300 {
301 self.get(name).map(str::parse)
302 }
303 /// Get the value of the header with the provided name, and
304 /// attempt to parse it using [`std::str::FromStr`].
305 ///
306 /// This is a variant of `get_as` that returns None on error,
307 /// intended to be used for cases where missing and invalid
308 /// headers should be treated the same. (With `get_as` this
309 /// requires `h.get_as(...).and_then(|r| r.ok())`, which is
310 /// somewhat opaque.
311 pub fn try_get<T, S>(&self, name: S) -> Option<T>
312 where
313 T: FromStr,
314 S: PartialEq<HeaderName>,
315 {
316 self.get(name).and_then(|val| val.parse::<T>().ok())
317 }
318
319 /// Get an iterator over the headers in no particular order.
320 ///
321 /// Note that we also implement IntoIterator.
322 pub fn iter(&self) -> <&Headers as IntoIterator>::IntoIter {
323 self.into_iter()
324 }
325}
326
327impl std::iter::IntoIterator for Headers {
328 type IntoIter = <Vec<Header> as IntoIterator>::IntoIter;
329 type Item = Header;
330 fn into_iter(self) -> Self::IntoIter {
331 self.headers.into_iter()
332 }
333}
334
335impl<'a> std::iter::IntoIterator for &'a Headers {
336 type IntoIter = <&'a [Header] as IntoIterator>::IntoIter;
337 type Item = &'a Header;
338 fn into_iter(self) -> Self::IntoIter {
339 self.headers[..].iter()
340 }
341}
342
343impl FromIterator<Header> for Headers {
344 fn from_iter<T>(iter: T) -> Self
345 where
346 T: IntoIterator<Item = Header>,
347 {
348 let mut v = iter.into_iter().collect::<Vec<Header>>();
349 v.sort_by(|a, b| a.name.cmp(&b.name));
350 v.reverse();
351 v.dedup_by(|a, b| a.name == b.name);
352 v.into()
353 }
354}
355
356impl From<Vec<Header>> for Headers {
357 fn from(headers: Vec<Header>) -> Self {
358 Self { headers }
359 }
360}
361
362#[allow(clippy::implicit_hasher)] // https://github.com/rust-lang/rust-clippy/issues/3899
363impl From<Headers> for HashMap<String, String> {
364 fn from(headers: Headers) -> HashMap<String, String> {
365 headers
366 .into_iter()
367 .map(|h| (String::from(h.name), h.value))
368 .collect()
369 }
370}
371
372pub mod consts {
373 use super::name::HeaderName;
374 macro_rules! def_header_consts {
375 ($(($NAME:ident, $string:literal)),* $(,)?) => {
376 $(pub const $NAME: HeaderName = HeaderName(std::borrow::Cow::Borrowed($string));)*
377 };
378 }
379
380 macro_rules! headers {
381 ($(($NAME:ident, $string:literal)),* $(,)?) => {
382 def_header_consts!($(($NAME, $string)),*);
383 // Unused except for tests.
384 const _ALL: &[&str] = &[$($string),*];
385 };
386 }
387
388 // Predefined header names, for convenience.
389 // Feel free to add to these.
390 headers!(
391 (ACCEPT_ENCODING, "accept-encoding"),
392 (ACCEPT, "accept"),
393 (AUTHORIZATION, "authorization"),
394 (CONTENT_TYPE, "content-type"),
395 (ETAG, "etag"),
396 (IF_NONE_MATCH, "if-none-match"),
397 (USER_AGENT, "user-agent"),
398 // non-standard, but it's convenient to have these.
399 (RETRY_AFTER, "retry-after"),
400 (X_IF_UNMODIFIED_SINCE, "x-if-unmodified-since"),
401 (X_KEYID, "x-keyid"),
402 (X_LAST_MODIFIED, "x-last-modified"),
403 (X_TIMESTAMP, "x-timestamp"),
404 (X_WEAVE_NEXT_OFFSET, "x-weave-next-offset"),
405 (X_WEAVE_RECORDS, "x-weave-records"),
406 (X_WEAVE_TIMESTAMP, "x-weave-timestamp"),
407 (X_WEAVE_BACKOFF, "x-weave-backoff"),
408 );
409
410 #[test]
411 fn test_predefined() {
412 for &name in _ALL {
413 assert!(
414 HeaderName::new(name).is_ok(),
415 "Invalid header name in predefined header constants: {}",
416 name
417 );
418 assert_eq!(
419 name.to_ascii_lowercase(),
420 name,
421 "Non-lowercase name in predefined header constants: {}",
422 name
423 );
424 }
425 }
426}