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#[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 pub const NULL: Origin = Origin(OriginOrNull::Null);
45
46 #[inline]
48 pub fn is_null(&self) -> bool {
49 match self.0 {
50 OriginOrNull::Null => true,
51 _ => false,
52 }
53 }
54
55 #[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 #[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 #[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 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 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 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}