headers/common/
origin.rs

1use std::convert::TryFrom;
2use std::fmt;
3
4use bytes::Bytes;
5use http::uri::{self, Authority, Scheme, Uri};
6
7use util::{IterExt, TryFromValues};
8use HeaderValue;
9
10/// The `Origin` header.
11///
12/// The `Origin` header is a version of the `Referer` header that is used for all HTTP fetches and `POST`s whose CORS flag is set.
13/// This header is often used to inform recipients of the security context of where the request was initiated.
14///
15/// Following the spec, [https://fetch.spec.whatwg.org/#origin-header][url], the value of this header is composed of
16/// a String (scheme), Host (host/port)
17///
18/// [url]: https://fetch.spec.whatwg.org/#origin-header
19///
20/// # Examples
21///
22/// ```
23/// # extern crate headers;
24/// use headers::Origin;
25///
26/// let origin = Origin::NULL;
27/// ```
28#[derive(Clone, Debug, PartialEq, Eq, Hash)]
29pub struct Origin(OriginOrNull);
30
31derive_header! {
32    Origin(_),
33    name: ORIGIN
34}
35
36#[derive(Clone, Debug, PartialEq, Eq, Hash)]
37enum OriginOrNull {
38    Origin(Scheme, Authority),
39    Null,
40}
41
42impl Origin {
43    /// The literal `null` Origin header.
44    pub const NULL: Origin = Origin(OriginOrNull::Null);
45
46    /// Checks if `Origin` is `null`.
47    #[inline]
48    pub fn is_null(&self) -> bool {
49        match self.0 {
50            OriginOrNull::Null => true,
51            _ => false,
52        }
53    }
54
55    /// Get the "scheme" part of this origin.
56    #[inline]
57    pub fn scheme(&self) -> &str {
58        match self.0 {
59            OriginOrNull::Origin(ref scheme, _) => scheme.as_str(),
60            OriginOrNull::Null => "",
61        }
62    }
63
64    /// Get the "hostname" part of this origin.
65    #[inline]
66    pub fn hostname(&self) -> &str {
67        match self.0 {
68            OriginOrNull::Origin(_, ref auth) => auth.host(),
69            OriginOrNull::Null => "",
70        }
71    }
72
73    /// Get the "port" part of this origin.
74    #[inline]
75    pub fn port(&self) -> Option<u16> {
76        match self.0 {
77            OriginOrNull::Origin(_, ref auth) => auth.port_u16(),
78            OriginOrNull::Null => None,
79        }
80    }
81
82    /// Tries to build a `Origin` from three parts, the scheme, the host and an optional port.
83    pub fn try_from_parts(
84        scheme: &str,
85        host: &str,
86        port: impl Into<Option<u16>>,
87    ) -> Result<Self, InvalidOrigin> {
88        struct MaybePort(Option<u16>);
89
90        impl fmt::Display for MaybePort {
91            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92                if let Some(port) = self.0 {
93                    write!(f, ":{}", port)
94                } else {
95                    Ok(())
96                }
97            }
98        }
99
100        let bytes = Bytes::from(format!("{}://{}{}", scheme, host, MaybePort(port.into())));
101        HeaderValue::from_maybe_shared(bytes)
102            .ok()
103            .and_then(|val| Self::try_from_value(&val))
104            .ok_or_else(|| InvalidOrigin { _inner: () })
105    }
106
107    // Used in AccessControlAllowOrigin
108    pub(super) fn try_from_value(value: &HeaderValue) -> Option<Self> {
109        OriginOrNull::try_from_value(value).map(Origin)
110    }
111
112    pub(super) fn into_value(&self) -> HeaderValue {
113        (&self.0).into()
114    }
115}
116
117impl fmt::Display for Origin {
118    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
119        match self.0 {
120            OriginOrNull::Origin(ref scheme, ref auth) => write!(f, "{}://{}", scheme, auth),
121            OriginOrNull::Null => f.write_str("null"),
122        }
123    }
124}
125
126error_type!(InvalidOrigin);
127
128impl OriginOrNull {
129    fn try_from_value(value: &HeaderValue) -> Option<Self> {
130        if value == "null" {
131            return Some(OriginOrNull::Null);
132        }
133
134        let uri = Uri::try_from(value.as_bytes()).ok()?;
135
136        let (scheme, auth) = match uri.into_parts() {
137            uri::Parts {
138                scheme: Some(scheme),
139                authority: Some(auth),
140                path_and_query: None,
141                ..
142            } => (scheme, auth),
143            uri::Parts {
144                scheme: Some(ref scheme),
145                authority: Some(ref auth),
146                path_and_query: Some(ref p),
147                ..
148            } if p == "/" => (scheme.clone(), auth.clone()),
149            _ => {
150                return None;
151            }
152        };
153
154        Some(OriginOrNull::Origin(scheme, auth))
155    }
156}
157
158impl TryFromValues for OriginOrNull {
159    fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error>
160    where
161        I: Iterator<Item = &'i HeaderValue>,
162    {
163        values
164            .just_one()
165            .and_then(OriginOrNull::try_from_value)
166            .ok_or_else(::Error::invalid)
167    }
168}
169
170impl<'a> From<&'a OriginOrNull> for HeaderValue {
171    fn from(origin: &'a OriginOrNull) -> HeaderValue {
172        match origin {
173            OriginOrNull::Origin(ref scheme, ref auth) => {
174                let s = format!("{}://{}", scheme, auth);
175                let bytes = Bytes::from(s);
176                HeaderValue::from_maybe_shared(bytes)
177                    .expect("Scheme and Authority are valid header values")
178            }
179            // Serialized as "null" per ASCII serialization of an origin
180            // https://html.spec.whatwg.org/multipage/browsers.html#ascii-serialisation-of-an-origin
181            OriginOrNull::Null => HeaderValue::from_static("null"),
182        }
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::super::{test_decode, test_encode};
189    use super::*;
190
191    #[test]
192    fn origin() {
193        let s = "http://web-platform.test:8000";
194        let origin = test_decode::<Origin>(&[s]).unwrap();
195        assert_eq!(origin.scheme(), "http");
196        assert_eq!(origin.hostname(), "web-platform.test");
197        assert_eq!(origin.port(), Some(8000));
198
199        let headers = test_encode(origin);
200        assert_eq!(headers["origin"], s);
201    }
202
203    #[test]
204    fn null() {
205        assert_eq!(test_decode::<Origin>(&["null"]), Some(Origin::NULL),);
206
207        let headers = test_encode(Origin::NULL);
208        assert_eq!(headers["origin"], "null");
209    }
210}