Skip to content

Commit 67a4335

Browse files
authored
Merge pull request #209 from http-rs/timing-allow-origin
Timing allow origin
2 parents a50b9b9 + babd65e commit 67a4335

File tree

6 files changed

+348
-12
lines changed

6 files changed

+348
-12
lines changed

src/headers/constants.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ pub const PRAGMA: HeaderName = HeaderName::from_lowercase_str("pragma");
125125

126126
/// The `Proxy-Authenticate` Header
127127
pub const PROXY_AUTHENTICATE: HeaderName = HeaderName::from_lowercase_str("proxy-authenticate");
128+
128129
/// The `Proxy-Authorization` Header
129130
pub const PROXY_AUTHORIZATION: HeaderName = HeaderName::from_lowercase_str("proxy-authorization");
130131

@@ -143,6 +144,9 @@ pub const SERVER_TIMING: HeaderName = HeaderName::from_lowercase_str("server-tim
143144
/// The `Te` Header
144145
pub const TE: HeaderName = HeaderName::from_lowercase_str("te");
145146

147+
/// The `Timing-Allow-Origin` Header
148+
pub const TIMING_ALLOW_ORIGIN: HeaderName = HeaderName::from_lowercase_str("timing-allow-origin");
149+
146150
/// The `Traceparent` Header
147151
pub const TRACEPARENT: HeaderName = HeaderName::from_lowercase_str("traceparent");
148152

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,9 @@ mod status;
131131
mod status_code;
132132
mod version;
133133

134+
pub mod trace;
134135
cfg_unstable! {
135136
pub mod upgrade;
136-
pub mod trace;
137137

138138
mod client;
139139
mod server;

src/trace/allow_origin.rs

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
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+
29+
use crate::headers::{HeaderName, HeaderValue, Headers, ToHeaderValues, TIMING_ALLOW_ORIGIN};
30+
use crate::{Status, Url};
31+
32+
use std::fmt::Write;
33+
use std::fmt::{self, Debug};
34+
use std::iter::Iterator;
35+
use std::option;
36+
use std::slice;
37+
38+
/// Specify origins that are allowed to see values via the Resource Timing API.
39+
///
40+
/// # Examples
41+
///
42+
/// ```
43+
/// # fn main() -> http_types::Result<()> {
44+
/// #
45+
/// use http_types::Response;
46+
/// use http_types::trace::{AllowOrigin, Origin};
47+
///
48+
/// let mut origins = AllowOrigin::new();
49+
/// origins.push(Origin::Wildcard);
50+
///
51+
/// let mut res = Response::new(200);
52+
/// origins.apply(&mut res);
53+
///
54+
/// let origins = AllowOrigin::from_headers(res)?.unwrap();
55+
/// let origin = origins.iter().next().unwrap();
56+
/// assert_eq!(origin, &Origin::Wildcard);
57+
/// #
58+
/// # Ok(()) }
59+
/// ```
60+
#[derive(Clone, Eq, PartialEq)]
61+
pub struct AllowOrigin {
62+
origins: Vec<Origin>,
63+
}
64+
65+
impl AllowOrigin {
66+
/// Create a new instance of `AllowOrigin`.
67+
pub fn new() -> Self {
68+
Self { origins: vec![] }
69+
}
70+
71+
/// Create an instance of `AllowOrigin` from a `Headers` instance.
72+
///
73+
/// # Implementation note
74+
///
75+
/// A header value of `"null"` is treated the same as if no header was sent.
76+
pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
77+
let headers = match headers.as_ref().get(TIMING_ALLOW_ORIGIN) {
78+
Some(headers) => headers,
79+
None => return Ok(None),
80+
};
81+
82+
let mut origins = vec![];
83+
for header in headers {
84+
for origin in header.as_str().split(',') {
85+
match origin.trim_start() {
86+
"*" => origins.push(Origin::Wildcard),
87+
r#""null""# => continue,
88+
origin => {
89+
let url = Url::parse(origin).status(400)?;
90+
origins.push(Origin::Url(url));
91+
}
92+
}
93+
}
94+
}
95+
96+
Ok(Some(Self { origins }))
97+
}
98+
99+
/// Append an origin to the list of origins.
100+
pub fn push(&mut self, origin: impl Into<Origin>) {
101+
self.origins.push(origin.into());
102+
}
103+
104+
/// Insert a `HeaderName` + `HeaderValue` pair into a `Headers` instance.
105+
pub fn apply(&self, mut headers: impl AsMut<Headers>) {
106+
headers.as_mut().insert(TIMING_ALLOW_ORIGIN, self.value());
107+
}
108+
109+
/// Get the `HeaderName`.
110+
pub fn name(&self) -> HeaderName {
111+
TIMING_ALLOW_ORIGIN
112+
}
113+
114+
/// Get the `HeaderValue`.
115+
pub fn value(&self) -> HeaderValue {
116+
let mut output = String::new();
117+
for (n, origin) in self.origins.iter().enumerate() {
118+
let origin: HeaderValue = origin.clone().into();
119+
match n {
120+
0 => write!(output, "{}", origin).unwrap(),
121+
_ => write!(output, ", {}", origin).unwrap(),
122+
};
123+
}
124+
125+
// SAFETY: the internal string is validated to be ASCII.
126+
unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
127+
}
128+
129+
/// An iterator visiting all server timings.
130+
pub fn iter(&self) -> Iter<'_> {
131+
Iter {
132+
inner: self.origins.iter(),
133+
}
134+
}
135+
136+
/// An iterator visiting all server timings.
137+
pub fn iter_mut(&mut self) -> IterMut<'_> {
138+
IterMut {
139+
inner: self.origins.iter_mut(),
140+
}
141+
}
142+
}
143+
144+
impl IntoIterator for AllowOrigin {
145+
type Item = Origin;
146+
type IntoIter = IntoIter;
147+
148+
#[inline]
149+
fn into_iter(self) -> Self::IntoIter {
150+
IntoIter {
151+
inner: self.origins.into_iter(),
152+
}
153+
}
154+
}
155+
156+
impl<'a> IntoIterator for &'a AllowOrigin {
157+
type Item = &'a Origin;
158+
type IntoIter = Iter<'a>;
159+
160+
// #[inline]serv
161+
fn into_iter(self) -> Self::IntoIter {
162+
self.iter()
163+
}
164+
}
165+
166+
impl<'a> IntoIterator for &'a mut AllowOrigin {
167+
type Item = &'a mut Origin;
168+
type IntoIter = IterMut<'a>;
169+
170+
#[inline]
171+
fn into_iter(self) -> Self::IntoIter {
172+
self.iter_mut()
173+
}
174+
}
175+
176+
/// A borrowing iterator over entries in `AllowOrigin`.
177+
#[derive(Debug)]
178+
pub struct IntoIter {
179+
inner: std::vec::IntoIter<Origin>,
180+
}
181+
182+
impl Iterator for IntoIter {
183+
type Item = Origin;
184+
185+
fn next(&mut self) -> Option<Self::Item> {
186+
self.inner.next()
187+
}
188+
189+
#[inline]
190+
fn size_hint(&self) -> (usize, Option<usize>) {
191+
self.inner.size_hint()
192+
}
193+
}
194+
195+
/// A lending iterator over entries in `AllowOrigin`.
196+
#[derive(Debug)]
197+
pub struct Iter<'a> {
198+
inner: slice::Iter<'a, Origin>,
199+
}
200+
201+
impl<'a> Iterator for Iter<'a> {
202+
type Item = &'a Origin;
203+
204+
fn next(&mut self) -> Option<Self::Item> {
205+
self.inner.next()
206+
}
207+
208+
#[inline]
209+
fn size_hint(&self) -> (usize, Option<usize>) {
210+
self.inner.size_hint()
211+
}
212+
}
213+
214+
/// A mutable iterator over entries in `AllowOrigin`.
215+
#[derive(Debug)]
216+
pub struct IterMut<'a> {
217+
inner: slice::IterMut<'a, Origin>,
218+
}
219+
220+
impl<'a> Iterator for IterMut<'a> {
221+
type Item = &'a mut Origin;
222+
223+
fn next(&mut self) -> Option<Self::Item> {
224+
self.inner.next()
225+
}
226+
227+
#[inline]
228+
fn size_hint(&self) -> (usize, Option<usize>) {
229+
self.inner.size_hint()
230+
}
231+
}
232+
233+
// Conversion from `AllowOrigin` -> `HeaderValue`.
234+
impl ToHeaderValues for AllowOrigin {
235+
type Iter = option::IntoIter<HeaderValue>;
236+
fn to_header_values(&self) -> crate::Result<Self::Iter> {
237+
Ok(self.value().to_header_values().unwrap())
238+
}
239+
}
240+
241+
impl Debug for AllowOrigin {
242+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243+
let mut list = f.debug_list();
244+
for origin in &self.origins {
245+
list.entry(origin);
246+
}
247+
list.finish()
248+
}
249+
}
250+
251+
/// An origin passed into `AllowOrigin`.
252+
///
253+
/// Values can either be `Url` or `Wildcard`. `"null"` values are skipped during parsing.
254+
//
255+
// NOTE: this origin is different than the origin in the fetch spec. It needs to
256+
// be its own type.
257+
#[derive(Debug, Clone, Eq, PartialEq)]
258+
pub enum Origin {
259+
/// An origin URL.
260+
Url(Url),
261+
/// Allow all origins.
262+
Wildcard,
263+
}
264+
265+
impl From<Url> for Origin {
266+
fn from(url: Url) -> Self {
267+
Origin::Url(url)
268+
}
269+
}
270+
271+
impl From<Origin> for HeaderValue {
272+
fn from(entry: Origin) -> HeaderValue {
273+
unsafe {
274+
match entry {
275+
Origin::Url(url) => {
276+
HeaderValue::from_bytes_unchecked(format!("{}", url).into_bytes())
277+
}
278+
Origin::Wildcard => {
279+
HeaderValue::from_bytes_unchecked(String::from("*").into_bytes())
280+
}
281+
}
282+
}
283+
}
284+
}
285+
286+
#[cfg(test)]
287+
mod test {
288+
use super::*;
289+
use crate::headers::Headers;
290+
291+
#[test]
292+
fn smoke() -> crate::Result<()> {
293+
let mut origins = AllowOrigin::new();
294+
origins.push(Origin::Wildcard);
295+
296+
let mut headers = Headers::new();
297+
origins.apply(&mut headers);
298+
299+
let origins = AllowOrigin::from_headers(headers)?.unwrap();
300+
let origin = origins.iter().next().unwrap();
301+
assert_eq!(origin, &Origin::Wildcard);
302+
Ok(())
303+
}
304+
305+
#[test]
306+
fn multi() -> crate::Result<()> {
307+
let mut origins = AllowOrigin::new();
308+
origins.push(Origin::Wildcard);
309+
origins.push(Origin::Url(Url::parse("https://mozilla.org/")?));
310+
311+
let mut headers = Headers::new();
312+
origins.apply(&mut headers);
313+
314+
let origins = AllowOrigin::from_headers(headers)?.unwrap();
315+
let mut origins = origins.iter();
316+
let origin = origins.next().unwrap();
317+
assert!(matches!(origin, Origin::Wildcard));
318+
319+
let origin = origins.next().unwrap();
320+
let rhs = Url::parse("https://mozilla.org/")?;
321+
assert_eq!(origin, &Origin::Url(rhs));
322+
Ok(())
323+
}
324+
325+
#[test]
326+
fn bad_request_on_parse_error() -> crate::Result<()> {
327+
let mut headers = Headers::new();
328+
headers.insert(TIMING_ALLOW_ORIGIN, "server; <nori ate your param omnom>");
329+
let err = AllowOrigin::from_headers(headers).unwrap_err();
330+
assert_eq!(err.status(), 400);
331+
Ok(())
332+
}
333+
}

src/trace/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@
66
//!
77
//! # Specifications
88
//!
9-
//! - [W3C Trace-Context headers](https://w3c.github.io/trace-context/)
10-
//! - [W3C Server-Timing headers](https://w3c.github.io/server-timing/#the-server-timing-header-field)
9+
//! - [W3C Trace-Context header](https://w3c.github.io/trace-context/)
10+
//! - [W3C Server-Timing header](https://w3c.github.io/server-timing/#the-server-timing-header-field)
11+
//! - [W3C Timing-Allow-Origin header](https://w3c.github.io/resource-timing/#sec-timing-allow-origin)
1112
13+
pub mod allow_origin;
1214
pub mod server_timing;
1315
mod trace_context;
1416

17+
#[doc(inline)]
18+
pub use allow_origin::{AllowOrigin, Origin};
1519
#[doc(inline)]
1620
pub use server_timing::{Metric, ServerTiming};
1721
pub use trace_context::TraceContext;

src/trace/server_timing/metric.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ impl Metric {
4848

4949
/// The timing description.
5050
pub fn description(&self) -> Option<&str> {
51-
self.desc.as_ref().map(|s| s.as_str())
51+
self.desc.as_deref()
5252
}
5353
}
5454

0 commit comments

Comments
 (0)