Skip to main content

rama_http_headers/common/
access_control_allow_origin.rs

1use rama_http_types::HeaderValue;
2use std::convert::TryFrom;
3
4use super::origin::Origin;
5use crate::Error;
6use crate::util::{IterExt, TryFromValues};
7
8/// `Access-Control-Allow-Origin` header, as defined on
9/// [mdn](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Access-Control-Allow-Origin).
10///
11/// The `Access-Control-Allow-Origin` header indicates whether a resource
12/// can be shared based by returning the value of the Origin request header,
13/// `*`, or `null` in the response.
14///
15/// ## ABNF
16///
17/// ```text
18/// Access-Control-Allow-Origin = "Access-Control-Allow-Origin" ":" origin-list-or-null | "*"
19/// ```
20///
21/// ## Example values
22/// * `null`
23/// * `*`
24/// * `http://google.com/`
25///
26/// # Examples
27///
28/// ```
29/// use rama_http_headers::AccessControlAllowOrigin;
30/// use std::convert::TryFrom;
31///
32/// let any_origin = AccessControlAllowOrigin::ANY;
33/// let null_origin = AccessControlAllowOrigin::NULL;
34/// let origin = AccessControlAllowOrigin::try_from("http://web-platform.test:8000");
35/// ```
36#[derive(Clone, Debug, PartialEq, Eq, Hash)]
37pub struct AccessControlAllowOrigin(OriginOrAny);
38
39impl crate::TypedHeader for AccessControlAllowOrigin {
40    fn name() -> &'static ::rama_http_types::header::HeaderName {
41        &::rama_http_types::header::ACCESS_CONTROL_ALLOW_ORIGIN
42    }
43}
44
45impl crate::HeaderDecode for AccessControlAllowOrigin {
46    fn decode<'i, I>(values: &mut I) -> Result<Self, crate::Error>
47    where
48        I: Iterator<Item = &'i ::rama_http_types::header::HeaderValue>,
49    {
50        crate::util::TryFromValues::try_from_values(values).map(AccessControlAllowOrigin)
51    }
52}
53
54impl crate::HeaderEncode for AccessControlAllowOrigin {
55    fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
56        match &self.0 {
57            OriginOrAny::Origin(origin) => origin.encode(values),
58            OriginOrAny::Any => values.extend(::std::iter::once(HeaderValue::from_static("*"))),
59        }
60    }
61}
62
63#[derive(Clone, Debug, PartialEq, Eq, Hash)]
64enum OriginOrAny {
65    Origin(Origin),
66    /// Allow all origins
67    Any,
68}
69
70impl AccessControlAllowOrigin {
71    /// `Access-Control-Allow-Origin: *`
72    pub const ANY: Self = Self(OriginOrAny::Any);
73    /// `Access-Control-Allow-Origin: null`
74    pub const NULL: Self = Self(OriginOrAny::Origin(Origin::NULL));
75
76    /// Returns the origin if there's one specified.
77    pub fn origin(&self) -> Option<&Origin> {
78        match self.0 {
79            OriginOrAny::Origin(ref origin) => Some(origin),
80            OriginOrAny::Any => None,
81        }
82    }
83
84    pub fn try_from_origin_header_value(header_value: &HeaderValue) -> Option<Self> {
85        let origin = Origin::try_from_header_value(header_value)?;
86        Some(Self(OriginOrAny::Origin(origin)))
87    }
88}
89
90impl TryFrom<&str> for AccessControlAllowOrigin {
91    type Error = Error;
92
93    fn try_from(s: &str) -> Result<Self, Error> {
94        let header_value = HeaderValue::from_str(s).map_err(|_e| Error::invalid())?;
95        let origin = OriginOrAny::try_from(&header_value)?;
96        Ok(Self(origin))
97    }
98}
99
100impl TryFrom<&HeaderValue> for OriginOrAny {
101    type Error = Error;
102
103    fn try_from(header_value: &HeaderValue) -> Result<Self, Error> {
104        Origin::try_from_header_value(header_value)
105            .map(OriginOrAny::Origin)
106            .ok_or_else(Error::invalid)
107    }
108}
109
110impl TryFromValues for OriginOrAny {
111    fn try_from_values<'i, I>(values: &mut I) -> Result<Self, Error>
112    where
113        I: Iterator<Item = &'i HeaderValue>,
114    {
115        values
116            .just_one()
117            .and_then(|value| {
118                if value == "*" {
119                    return Some(Self::Any);
120                }
121
122                Origin::try_from_header_value(value).map(OriginOrAny::Origin)
123            })
124            .ok_or_else(Error::invalid)
125    }
126}
127
128#[cfg(test)]
129mod tests {
130
131    use super::super::{test_decode, test_encode};
132    use super::*;
133
134    #[test]
135    fn origin() {
136        let s = "http://web-platform.test:8000";
137
138        let allow_origin = test_decode::<AccessControlAllowOrigin>(&[s]).unwrap();
139        {
140            let origin = allow_origin.origin().unwrap();
141            assert_eq!(origin.scheme(), "http");
142            assert_eq!(origin.hostname(), "web-platform.test");
143            assert_eq!(origin.port(), Some(8000));
144        }
145
146        let headers = test_encode(allow_origin);
147        assert_eq!(headers["access-control-allow-origin"], s);
148    }
149
150    #[test]
151    fn try_from_origin() {
152        let s = "http://web-platform.test:8000";
153
154        let allow_origin = AccessControlAllowOrigin::try_from(s).unwrap();
155        {
156            let origin = allow_origin.origin().unwrap();
157            assert_eq!(origin.scheme(), "http");
158            assert_eq!(origin.hostname(), "web-platform.test");
159            assert_eq!(origin.port(), Some(8000));
160        }
161
162        let headers = test_encode(allow_origin);
163        assert_eq!(headers["access-control-allow-origin"], s);
164    }
165
166    #[test]
167    fn any() {
168        let allow_origin = test_decode::<AccessControlAllowOrigin>(&["*"]).unwrap();
169        assert_eq!(allow_origin, AccessControlAllowOrigin::ANY);
170
171        let headers = test_encode(allow_origin);
172        assert_eq!(headers["access-control-allow-origin"], "*");
173    }
174
175    #[test]
176    fn null() {
177        let allow_origin = test_decode::<AccessControlAllowOrigin>(&["null"]).unwrap();
178        assert_eq!(allow_origin, AccessControlAllowOrigin::NULL);
179
180        let headers = test_encode(allow_origin);
181        assert_eq!(headers["access-control-allow-origin"], "null");
182    }
183}