http_types/security/
timing_allow_origin.rs

1//! Specify origins that are allowed to see values via the Resource Timing API.
2//!
3//! # Specifications
4//!
5//! - [W3C Timing-Allow-Origin header](https://w3c.github.io/resource-timing/#sec-timing-allow-origin)
6//! - [WhatWG Fetch Origin header](https://fetch.spec.whatwg.org/#origin-header)
7//!
8//! # Examples
9//!
10//! ```
11//! # fn main() -> http_types::Result<()> {
12//! #
13//! use http_types::{Response, Url};
14//! use http_types::security::TimingAllowOrigin;
15//!
16//! let mut origins = TimingAllowOrigin::new();
17//! origins.push(Url::parse("https://example.com")?);
18//!
19//! let mut res = Response::new(200);
20//! origins.apply(&mut res);
21//!
22//! let origins = TimingAllowOrigin::from_headers(res)?.unwrap();
23//! let origin = origins.iter().next().unwrap();
24//! assert_eq!(origin, &Url::parse("https://example.com")?);
25//! #
26//! # Ok(()) }
27//! ```
28
29use crate::headers::{HeaderName, HeaderValue, Headers, ToHeaderValues, TIMING_ALLOW_ORIGIN};
30use crate::{Status, Url};
31
32use std::fmt::Write;
33use std::fmt::{self, Debug};
34use std::iter::Iterator;
35use std::option;
36use std::slice;
37
38/// Specify origins that are allowed to see values via the Resource Timing API.
39///
40/// # Examples
41///
42/// ```
43/// # fn main() -> http_types::Result<()> {
44/// #
45/// use http_types::{Response, Url};
46/// use http_types::security::TimingAllowOrigin;
47///
48/// let mut origins = TimingAllowOrigin::new();
49/// origins.push(Url::parse("https://example.com")?);
50///
51/// let mut res = Response::new(200);
52/// origins.apply(&mut res);
53///
54/// let origins = TimingAllowOrigin::from_headers(res)?.unwrap();
55/// let origin = origins.iter().next().unwrap();
56/// assert_eq!(origin, &Url::parse("https://example.com")?);
57/// #
58/// # Ok(()) }
59/// ```
60#[derive(Clone, Eq, PartialEq)]
61pub struct TimingAllowOrigin {
62    origins: Vec<Url>,
63    wildcard: bool,
64}
65
66impl TimingAllowOrigin {
67    /// Create a new instance of `AllowOrigin`.
68    pub fn new() -> Self {
69        Self {
70            origins: vec![],
71            wildcard: false,
72        }
73    }
74
75    /// Create an instance of `AllowOrigin` from a `Headers` instance.
76    ///
77    /// # Implementation note
78    ///
79    /// A header value of `"null"` is treated the same as if no header was sent.
80    pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
81        let headers = match headers.as_ref().get(TIMING_ALLOW_ORIGIN) {
82            Some(headers) => headers,
83            None => return Ok(None),
84        };
85
86        let mut wildcard = false;
87        let mut origins = vec![];
88        for header in headers {
89            for origin in header.as_str().split(',') {
90                match origin.trim_start() {
91                    "*" => wildcard = true,
92                    r#""null""# => continue,
93                    origin => {
94                        let url = Url::parse(origin).status(400)?;
95                        origins.push(url);
96                    }
97                }
98            }
99        }
100
101        Ok(Some(Self { origins, wildcard }))
102    }
103
104    /// Append an origin to the list of origins.
105    pub fn push(&mut self, origin: impl Into<Url>) {
106        self.origins.push(origin.into());
107    }
108
109    /// Insert a `HeaderName` + `HeaderValue` pair into a `Headers` instance.
110    pub fn apply(&self, mut headers: impl AsMut<Headers>) {
111        headers.as_mut().insert(TIMING_ALLOW_ORIGIN, self.value());
112    }
113
114    /// Get the `HeaderName`.
115    pub fn name(&self) -> HeaderName {
116        TIMING_ALLOW_ORIGIN
117    }
118
119    /// Get the `HeaderValue`.
120    pub fn value(&self) -> HeaderValue {
121        let mut output = String::new();
122        for (n, origin) in self.origins.iter().enumerate() {
123            match n {
124                0 => write!(output, "{}", origin).unwrap(),
125                _ => write!(output, ", {}", origin).unwrap(),
126            };
127        }
128
129        if self.wildcard {
130            match output.len() {
131                0 => write!(output, "*").unwrap(),
132                _ => write!(output, ", *").unwrap(),
133            };
134        }
135
136        // SAFETY: the internal string is validated to be ASCII.
137        unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
138    }
139
140    /// Returns `true` if a wildcard directive was set.
141    pub fn wildcard(&self) -> bool {
142        self.wildcard
143    }
144
145    /// Set the wildcard directive.
146    pub fn set_wildcard(&mut self, wildcard: bool) {
147        self.wildcard = wildcard
148    }
149
150    /// An iterator visiting all server timings.
151    pub fn iter(&self) -> Iter<'_> {
152        Iter {
153            inner: self.origins.iter(),
154        }
155    }
156
157    /// An iterator visiting all server timings.
158    pub fn iter_mut(&mut self) -> IterMut<'_> {
159        IterMut {
160            inner: self.origins.iter_mut(),
161        }
162    }
163}
164
165impl IntoIterator for TimingAllowOrigin {
166    type Item = Url;
167    type IntoIter = IntoIter;
168
169    #[inline]
170    fn into_iter(self) -> Self::IntoIter {
171        IntoIter {
172            inner: self.origins.into_iter(),
173        }
174    }
175}
176
177impl<'a> IntoIterator for &'a TimingAllowOrigin {
178    type Item = &'a Url;
179    type IntoIter = Iter<'a>;
180
181    // #[inline]serv
182    fn into_iter(self) -> Self::IntoIter {
183        self.iter()
184    }
185}
186
187impl<'a> IntoIterator for &'a mut TimingAllowOrigin {
188    type Item = &'a mut Url;
189    type IntoIter = IterMut<'a>;
190
191    #[inline]
192    fn into_iter(self) -> Self::IntoIter {
193        self.iter_mut()
194    }
195}
196
197/// A borrowing iterator over entries in `AllowOrigin`.
198#[derive(Debug)]
199pub struct IntoIter {
200    inner: std::vec::IntoIter<Url>,
201}
202
203impl Iterator for IntoIter {
204    type Item = Url;
205
206    fn next(&mut self) -> Option<Self::Item> {
207        self.inner.next()
208    }
209
210    #[inline]
211    fn size_hint(&self) -> (usize, Option<usize>) {
212        self.inner.size_hint()
213    }
214}
215
216/// A lending iterator over entries in `AllowOrigin`.
217#[derive(Debug)]
218pub struct Iter<'a> {
219    inner: slice::Iter<'a, Url>,
220}
221
222impl<'a> Iterator for Iter<'a> {
223    type Item = &'a Url;
224
225    fn next(&mut self) -> Option<Self::Item> {
226        self.inner.next()
227    }
228
229    #[inline]
230    fn size_hint(&self) -> (usize, Option<usize>) {
231        self.inner.size_hint()
232    }
233}
234
235/// A mutable iterator over entries in `AllowOrigin`.
236#[derive(Debug)]
237pub struct IterMut<'a> {
238    inner: slice::IterMut<'a, Url>,
239}
240
241impl<'a> Iterator for IterMut<'a> {
242    type Item = &'a mut Url;
243
244    fn next(&mut self) -> Option<Self::Item> {
245        self.inner.next()
246    }
247
248    #[inline]
249    fn size_hint(&self) -> (usize, Option<usize>) {
250        self.inner.size_hint()
251    }
252}
253
254// Conversion from `AllowOrigin` -> `HeaderValue`.
255impl ToHeaderValues for TimingAllowOrigin {
256    type Iter = option::IntoIter<HeaderValue>;
257    fn to_header_values(&self) -> crate::Result<Self::Iter> {
258        Ok(self.value().to_header_values().unwrap())
259    }
260}
261
262impl Debug for TimingAllowOrigin {
263    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264        let mut list = f.debug_list();
265        for origin in &self.origins {
266            list.entry(origin);
267        }
268        list.finish()
269    }
270}
271
272#[cfg(test)]
273mod test {
274    use super::*;
275    use crate::headers::Headers;
276
277    #[test]
278    fn smoke() -> crate::Result<()> {
279        let mut origins = TimingAllowOrigin::new();
280        origins.push(Url::parse("https://example.com")?);
281
282        let mut headers = Headers::new();
283        origins.apply(&mut headers);
284
285        let origins = TimingAllowOrigin::from_headers(headers)?.unwrap();
286        let origin = origins.iter().next().unwrap();
287        assert_eq!(origin, &Url::parse("https://example.com")?);
288        Ok(())
289    }
290
291    #[test]
292    fn multi() -> crate::Result<()> {
293        let mut origins = TimingAllowOrigin::new();
294        origins.push(Url::parse("https://example.com")?);
295        origins.push(Url::parse("https://mozilla.org/")?);
296
297        let mut headers = Headers::new();
298        origins.apply(&mut headers);
299
300        let origins = TimingAllowOrigin::from_headers(headers)?.unwrap();
301        let mut origins = origins.iter();
302        let origin = origins.next().unwrap();
303        assert_eq!(origin, &Url::parse("https://example.com")?);
304
305        let origin = origins.next().unwrap();
306        assert_eq!(origin, &Url::parse("https://mozilla.org/")?);
307        Ok(())
308    }
309
310    #[test]
311    fn bad_request_on_parse_error() {
312        let mut headers = Headers::new();
313        headers.insert(TIMING_ALLOW_ORIGIN, "server; <nori ate your param omnom>");
314        let err = TimingAllowOrigin::from_headers(headers).unwrap_err();
315        assert_eq!(err.status(), 400);
316    }
317
318    #[test]
319    fn wildcard() -> crate::Result<()> {
320        let mut origins = TimingAllowOrigin::new();
321        origins.push(Url::parse("https://example.com")?);
322        origins.set_wildcard(true);
323
324        let mut headers = Headers::new();
325        origins.apply(&mut headers);
326
327        let origins = TimingAllowOrigin::from_headers(headers)?.unwrap();
328        assert!(origins.wildcard());
329        let origin = origins.iter().next().unwrap();
330        assert_eq!(origin, &Url::parse("https://example.com")?);
331        Ok(())
332    }
333}