Skip to content

Commit b0e460d

Browse files
committed
Add other::Referer header
1 parent ef5d1c8 commit b0e460d

File tree

2 files changed

+164
-0
lines changed

2 files changed

+164
-0
lines changed

src/other/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
33
mod date;
44
mod expect;
5+
mod referer;
56
mod source_map;
67

78
pub use date::Date;
89
pub use expect::Expect;
10+
pub use referer::Referer;
911
pub use source_map::SourceMap;

src/other/referer.rs

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
use crate::headers::{HeaderName, HeaderValue, Headers, REFERER};
2+
use crate::{bail_status as bail, Status, Url};
3+
4+
use std::convert::TryInto;
5+
6+
/// Contains the address of the page making the request.
7+
///
8+
/// __Important__: Although this header has many innocent uses it can have
9+
/// undesirable consequences for user security and privacy.
10+
///
11+
/// [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer)
12+
///
13+
/// # Specifications
14+
///
15+
/// - [RFC 7231, section 5.5.2: Referer](https://tools.ietf.org/html/rfc7231#section-5.5.2)
16+
///
17+
/// # Examples
18+
///
19+
/// ```
20+
/// # fn main() -> http_types::Result<()> {
21+
/// #
22+
/// use http_types::{Response, Url};
23+
/// use http_types::other::Referer;
24+
///
25+
/// let referer = Referer::new(Url::parse("https://example.net/")?);
26+
///
27+
/// let mut res = Response::new(200);
28+
/// referer.apply(&mut res);
29+
///
30+
/// let base_url = Url::parse("https://example.net/")?;
31+
/// let referer = Referer::from_headers(base_url, res)?.unwrap();
32+
/// assert_eq!(referer.location(), &Url::parse("https://example.net/")?);
33+
/// #
34+
/// # Ok(()) }
35+
/// ```
36+
#[derive(Debug)]
37+
pub struct Referer {
38+
location: Url,
39+
}
40+
41+
impl Referer {
42+
/// Create a new instance of `Referer` header.
43+
pub fn new(location: Url) -> Self {
44+
Self { location }
45+
}
46+
47+
/// Create a new instance from headers.
48+
pub fn from_headers<U>(base_url: U, headers: impl AsRef<Headers>) -> crate::Result<Option<Self>>
49+
where
50+
U: TryInto<Url>,
51+
U::Error: std::fmt::Debug,
52+
{
53+
let headers = match headers.as_ref().get(REFERER) {
54+
Some(headers) => headers,
55+
None => return Ok(None),
56+
};
57+
58+
// If we successfully parsed the header then there's always at least one
59+
// entry. We want the last entry.
60+
let header_value = headers.iter().last().unwrap();
61+
62+
let url = match Url::parse(header_value.as_str()) {
63+
Ok(url) => url,
64+
Err(_) => match base_url.try_into() {
65+
Ok(base_url) => base_url.join(header_value.as_str().trim()).status(400)?,
66+
Err(_) => bail!(400, "Invalid base url provided"),
67+
},
68+
};
69+
70+
Ok(Some(Self { location: url }))
71+
}
72+
73+
/// Sets the header.
74+
pub fn apply(&self, mut headers: impl AsMut<Headers>) {
75+
headers.as_mut().insert(self.name(), self.value());
76+
}
77+
78+
/// Get the `HeaderName`.
79+
pub fn name(&self) -> HeaderName {
80+
REFERER
81+
}
82+
83+
/// Get the `HeaderValue`.
84+
pub fn value(&self) -> HeaderValue {
85+
let output = self.location.to_string();
86+
87+
// SAFETY: the internal string is validated to be ASCII.
88+
unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
89+
}
90+
91+
/// Get the url.
92+
pub fn location(&self) -> &Url {
93+
&self.location
94+
}
95+
96+
/// Set the url.
97+
pub fn set_location<U>(&mut self, location: U)
98+
where
99+
U: TryInto<Url>,
100+
U::Error: std::fmt::Debug,
101+
{
102+
self.location = location
103+
.try_into()
104+
.expect("Could not convert into valid URL")
105+
}
106+
}
107+
108+
#[cfg(test)]
109+
mod test {
110+
use super::*;
111+
use crate::headers::Headers;
112+
113+
#[test]
114+
fn smoke() -> crate::Result<()> {
115+
let referer = Referer::new(Url::parse("https://example.net/test.json")?);
116+
117+
let mut headers = Headers::new();
118+
referer.apply(&mut headers);
119+
120+
let base_url = Url::parse("https://example.net/")?;
121+
let referer = Referer::from_headers(base_url, headers)?.unwrap();
122+
assert_eq!(
123+
referer.location(),
124+
&Url::parse("https://example.net/test.json")?
125+
);
126+
Ok(())
127+
}
128+
129+
#[test]
130+
fn bad_request_on_parse_error() -> crate::Result<()> {
131+
let mut headers = Headers::new();
132+
headers.insert(REFERER, "htt://<nori ate the tag. yum.>");
133+
let err =
134+
Referer::from_headers(Url::parse("https://example.net").unwrap(), headers).unwrap_err();
135+
assert_eq!(err.status(), 400);
136+
Ok(())
137+
}
138+
139+
#[test]
140+
fn fallback_works() -> crate::Result<()> {
141+
let mut headers = Headers::new();
142+
headers.insert(REFERER, "/test.json");
143+
144+
let base_url = Url::parse("https://fallback.net/")?;
145+
let referer = Referer::from_headers(base_url, headers)?.unwrap();
146+
assert_eq!(
147+
referer.location(),
148+
&Url::parse("https://fallback.net/test.json")?
149+
);
150+
151+
let mut headers = Headers::new();
152+
headers.insert(REFERER, "https://example.com/test.json");
153+
154+
let base_url = Url::parse("https://fallback.net/")?;
155+
let referer = Referer::from_headers(base_url, headers)?.unwrap();
156+
assert_eq!(
157+
referer.location(),
158+
&Url::parse("https://example.com/test.json")?
159+
);
160+
Ok(())
161+
}
162+
}

0 commit comments

Comments
 (0)