Skip to content

Commit 43aea71

Browse files
committed
Finalize Allow-Origin
1 parent 79f863b commit 43aea71

File tree

2 files changed

+247
-29
lines changed

2 files changed

+247
-29
lines changed

src/trace/allow_origin.rs

Lines changed: 245 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,42 @@
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;
14+
//! use http_types::trace::{AllowOrigin, Origin};
15+
//!
16+
//! let mut origins = AllowOrigin::new();
17+
//! origins.push(Origin::Wildcard);
18+
//!
19+
//! let mut res = Response::new(200);
20+
//! origins.apply(&mut res);
21+
//!
22+
//! let origins = AllowOrigin::from_headers(res)?.unwrap();
23+
//! let origin = origins.iter().next().unwrap();
24+
//! assert_eq!(origin, &Origin::Wildcard);
25+
//! #
26+
//! # Ok(()) }
27+
//! ```
28+
129
use crate::headers::{HeaderName, HeaderValue, Headers, ToHeaderValues, TIMING_ALLOW_ORIGIN};
2-
use crate::Url;
30+
use crate::{Status, Url};
31+
32+
use std::fmt::Write;
33+
use std::fmt::{self, Debug};
34+
use std::iter::Iterator;
335
use std::option;
36+
use std::slice;
437

5-
/// `Timing-Allow-Origin` header.
6-
///
7-
/// # Specifications
8-
///
9-
/// - [W3C Timing-Allow-Origin header](https://w3c.github.io/resource-timing/#sec-timing-allow-origin)
10-
/// - [WhatWG Fetch Origin header](https://fetch.spec.whatwg.org/#origin-header)
11-
#[derive(Debug, Clone, Eq, PartialEq)]
38+
/// Specify origins that are allowed to see values via the Resource Timing API.
39+
#[derive(Clone, Eq, PartialEq)]
1240
pub struct AllowOrigin {
1341
origins: Vec<Origin>,
1442
}
@@ -23,15 +51,28 @@ impl AllowOrigin {
2351
///
2452
/// # Implementation note
2553
///
26-
/// If a `"null"` value is found
54+
/// A header value of `"null"` is treated the same as if no header was sent.
2755
pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
28-
let allow_origin = match headers.as_ref().get(TIMING_ALLOW_ORIGIN) {
29-
Some(header) => header,
56+
let headers = match headers.as_ref().get(TIMING_ALLOW_ORIGIN) {
57+
Some(headers) => headers,
3058
None => return Ok(None),
3159
};
3260

33-
allow_origin.as_str().split(",");
34-
todo!();
61+
let mut origins = vec![];
62+
for header in headers {
63+
for origin in header.as_str().split(",") {
64+
match origin.trim_start() {
65+
"*" => origins.push(Origin::Wildcard),
66+
r#""null""# => continue,
67+
origin => {
68+
let url = Url::parse(origin).status(400)?;
69+
origins.push(Origin::Url(url));
70+
}
71+
}
72+
}
73+
}
74+
75+
Ok(Some(Self { origins }))
3576
}
3677

3778
/// Append an origin to the list of origins.
@@ -40,26 +81,154 @@ impl AllowOrigin {
4081
}
4182

4283
/// Insert a `HeaderName` + `HeaderValue` pair into a `Headers` instance.
43-
pub fn apply(&self, headers: impl AsMut<Headers>) {
44-
todo!();
84+
pub fn apply(&self, mut headers: impl AsMut<Headers>) {
85+
headers.as_mut().insert(TIMING_ALLOW_ORIGIN, self.value());
4586
}
4687

4788
/// Get the `HeaderName`.
4889
pub fn name(&self) -> HeaderName {
49-
todo!();
90+
TIMING_ALLOW_ORIGIN
5091
}
5192

5293
/// Get the `HeaderValue`.
5394
pub fn value(&self) -> HeaderValue {
54-
todo!();
95+
let mut output = String::new();
96+
for (n, origin) in self.origins.iter().enumerate() {
97+
let origin: HeaderValue = origin.clone().into();
98+
match n {
99+
0 => write!(output, "{}", origin).unwrap(),
100+
_ => write!(output, ", {}", origin).unwrap(),
101+
};
102+
}
103+
104+
// SAFETY: the internal string is validated to be ASCII.
105+
unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
106+
}
107+
108+
/// An iterator visiting all server timings.
109+
pub fn into_iter(self) -> IntoIter {
110+
IntoIter {
111+
inner: self.origins.into_iter(),
112+
}
113+
}
114+
115+
/// An iterator visiting all server timings.
116+
pub fn iter(&self) -> Iter<'_> {
117+
Iter {
118+
inner: self.origins.iter(),
119+
}
120+
}
121+
122+
/// An iterator visiting all server timings.
123+
pub fn iter_mut(&mut self) -> IterMut<'_> {
124+
IterMut {
125+
inner: self.origins.iter_mut(),
126+
}
127+
}
128+
}
129+
130+
impl IntoIterator for AllowOrigin {
131+
type Item = Origin;
132+
type IntoIter = IntoIter;
133+
134+
#[inline]
135+
fn into_iter(self) -> Self::IntoIter {
136+
self.into_iter()
137+
}
138+
}
139+
140+
impl<'a> IntoIterator for &'a AllowOrigin {
141+
type Item = &'a Origin;
142+
type IntoIter = Iter<'a>;
143+
144+
// #[inline]serv
145+
fn into_iter(self) -> Self::IntoIter {
146+
self.iter()
147+
}
148+
}
149+
150+
impl<'a> IntoIterator for &'a mut AllowOrigin {
151+
type Item = &'a mut Origin;
152+
type IntoIter = IterMut<'a>;
153+
154+
#[inline]
155+
fn into_iter(self) -> Self::IntoIter {
156+
self.iter_mut()
157+
}
158+
}
159+
160+
/// A borrowing iterator over entries in `AllowOrigin`.
161+
#[derive(Debug)]
162+
pub struct IntoIter {
163+
inner: std::vec::IntoIter<Origin>,
164+
}
165+
166+
impl Iterator for IntoIter {
167+
type Item = Origin;
168+
169+
fn next(&mut self) -> Option<Self::Item> {
170+
self.inner.next()
171+
}
172+
173+
#[inline]
174+
fn size_hint(&self) -> (usize, Option<usize>) {
175+
self.inner.size_hint()
176+
}
177+
}
178+
179+
/// A lending iterator over entries in `AllowOrigin`.
180+
#[derive(Debug)]
181+
pub struct Iter<'a> {
182+
inner: slice::Iter<'a, Origin>,
183+
}
184+
185+
impl<'a> Iterator for Iter<'a> {
186+
type Item = &'a Origin;
187+
188+
fn next(&mut self) -> Option<Self::Item> {
189+
self.inner.next()
190+
}
191+
192+
#[inline]
193+
fn size_hint(&self) -> (usize, Option<usize>) {
194+
self.inner.size_hint()
195+
}
196+
}
197+
198+
/// A mutable iterator over entries in `AllowOrigin`.
199+
#[derive(Debug)]
200+
pub struct IterMut<'a> {
201+
inner: slice::IterMut<'a, Origin>,
202+
}
203+
204+
impl<'a> Iterator for IterMut<'a> {
205+
type Item = &'a mut Origin;
206+
207+
fn next(&mut self) -> Option<Self::Item> {
208+
self.inner.next()
209+
}
210+
211+
#[inline]
212+
fn size_hint(&self) -> (usize, Option<usize>) {
213+
self.inner.size_hint()
55214
}
56215
}
57216

58217
// Conversion from `AllowOrigin` -> `HeaderValue`.
59218
impl ToHeaderValues for AllowOrigin {
60219
type Iter = option::IntoIter<HeaderValue>;
61220
fn to_header_values(&self) -> crate::Result<Self::Iter> {
62-
todo!()
221+
Ok(self.value().to_header_values().unwrap())
222+
}
223+
}
224+
225+
impl Debug for AllowOrigin {
226+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227+
let mut list = f.debug_list();
228+
for origin in &self.origins {
229+
list.entry(origin);
230+
}
231+
list.finish()
63232
}
64233
}
65234

@@ -83,18 +252,66 @@ impl From<Url> for Origin {
83252
}
84253
}
85254

86-
// Conversion from `AllowOrigin` -> `HeaderValue`.
87-
impl ToHeaderValues for Origin {
88-
type Iter = option::IntoIter<HeaderValue>;
89-
fn to_header_values(&self) -> crate::Result<Self::Iter> {
90-
let res = unsafe {
91-
match self {
92-
Self::Url(url) => {
255+
impl From<Origin> for HeaderValue {
256+
fn from(entry: Origin) -> HeaderValue {
257+
unsafe {
258+
match entry {
259+
Origin::Url(url) => {
93260
HeaderValue::from_bytes_unchecked(format!("{}", url).into_bytes())
94261
}
95-
Self::Wildcard => HeaderValue::from_bytes_unchecked(String::from("*").into_bytes()),
262+
Origin::Wildcard => {
263+
HeaderValue::from_bytes_unchecked(String::from("*").into_bytes())
264+
}
96265
}
97-
};
98-
Ok(Some(res).into_iter())
266+
}
267+
}
268+
}
269+
270+
#[cfg(test)]
271+
mod test {
272+
use super::*;
273+
use crate::headers::Headers;
274+
275+
#[test]
276+
fn smoke() -> crate::Result<()> {
277+
let mut origins = AllowOrigin::new();
278+
origins.push(Origin::Wildcard);
279+
280+
let mut headers = Headers::new();
281+
origins.apply(&mut headers);
282+
283+
let origins = AllowOrigin::from_headers(headers)?.unwrap();
284+
let origin = origins.iter().next().unwrap();
285+
assert_eq!(origin, &Origin::Wildcard);
286+
Ok(())
287+
}
288+
289+
#[test]
290+
fn multi() -> crate::Result<()> {
291+
let mut origins = AllowOrigin::new();
292+
origins.push(Origin::Wildcard);
293+
origins.push(Origin::Url(Url::parse("https://mozilla.org/")?));
294+
295+
let mut headers = Headers::new();
296+
origins.apply(&mut headers);
297+
298+
let origins = AllowOrigin::from_headers(headers)?.unwrap();
299+
let mut origins = origins.iter();
300+
let origin = origins.next().unwrap();
301+
assert!(matches!(origin, Origin::Wildcard));
302+
303+
let origin = origins.next().unwrap();
304+
let rhs = Url::parse("https://mozilla.org/")?;
305+
assert_eq!(origin, &Origin::Url(rhs));
306+
Ok(())
307+
}
308+
309+
#[test]
310+
fn bad_request_on_parse_error() -> crate::Result<()> {
311+
let mut headers = Headers::new();
312+
headers.insert(TIMING_ALLOW_ORIGIN, "server; <nori ate your param omnom>");
313+
let err = AllowOrigin::from_headers(headers).unwrap_err();
314+
assert_eq!(err.status(), 400);
315+
Ok(())
99316
}
100317
}

src/trace/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@
1010
//! - [W3C Server-Timing header](https://w3c.github.io/server-timing/#the-server-timing-header-field)
1111
//! - [W3C Timing-Allow-Origin header](https://w3c.github.io/resource-timing/#sec-timing-allow-origin)
1212
13-
mod allow_origin;
13+
pub mod allow_origin;
1414
pub mod server_timing;
1515
mod trace_context;
1616

17+
#[doc(inline)]
1718
pub use allow_origin::{AllowOrigin, Origin};
1819
#[doc(inline)]
1920
pub use server_timing::{Metric, ServerTiming};

0 commit comments

Comments
 (0)