headers_ext/common/
origin.rs

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