Skip to content

Commit d8cb1e8

Browse files
authored
Merge pull request #277 from http-rs/referer
Add `other::Referer` header
2 parents c40a529 + cbcb0ee commit d8cb1e8

File tree

3 files changed

+165
-2
lines changed

3 files changed

+165
-2
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: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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(500)?,
66+
Err(_) => bail!(500, "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) -> Result<(), U::Error>
98+
where
99+
U: TryInto<Url>,
100+
U::Error: std::fmt::Debug,
101+
{
102+
self.location = location.try_into()?;
103+
Ok(())
104+
}
105+
}
106+
107+
#[cfg(test)]
108+
mod test {
109+
use super::*;
110+
use crate::headers::Headers;
111+
112+
#[test]
113+
fn smoke() -> crate::Result<()> {
114+
let referer = Referer::new(Url::parse("https://example.net/test.json")?);
115+
116+
let mut headers = Headers::new();
117+
referer.apply(&mut headers);
118+
119+
let base_url = Url::parse("https://example.net/")?;
120+
let referer = Referer::from_headers(base_url, headers)?.unwrap();
121+
assert_eq!(
122+
referer.location(),
123+
&Url::parse("https://example.net/test.json")?
124+
);
125+
Ok(())
126+
}
127+
128+
#[test]
129+
fn bad_request_on_parse_error() -> crate::Result<()> {
130+
let mut headers = Headers::new();
131+
headers.insert(REFERER, "htt://<nori ate the tag. yum.>");
132+
let err =
133+
Referer::from_headers(Url::parse("https://example.net").unwrap(), headers).unwrap_err();
134+
assert_eq!(err.status(), 500);
135+
Ok(())
136+
}
137+
138+
#[test]
139+
fn fallback_works() -> crate::Result<()> {
140+
let mut headers = Headers::new();
141+
headers.insert(REFERER, "/test.json");
142+
143+
let base_url = Url::parse("https://fallback.net/")?;
144+
let referer = Referer::from_headers(base_url, headers)?.unwrap();
145+
assert_eq!(
146+
referer.location(),
147+
&Url::parse("https://fallback.net/test.json")?
148+
);
149+
150+
let mut headers = Headers::new();
151+
headers.insert(REFERER, "https://example.com/test.json");
152+
153+
let base_url = Url::parse("https://fallback.net/")?;
154+
let referer = Referer::from_headers(base_url, headers)?.unwrap();
155+
assert_eq!(
156+
referer.location(),
157+
&Url::parse("https://example.com/test.json")?
158+
);
159+
Ok(())
160+
}
161+
}

src/other/source_map.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ impl SourceMap {
5959
let url = match Url::parse(header_value.as_str()) {
6060
Ok(url) => url,
6161
Err(_) => match base_url.try_into() {
62-
Ok(base_url) => base_url.join(header_value.as_str().trim()).status(400)?,
62+
Ok(base_url) => base_url.join(header_value.as_str().trim()).status(500)?,
6363
Err(_) => bail!(500, "Invalid base url provided"),
6464
},
6565
};
@@ -128,7 +128,7 @@ mod test {
128128
headers.insert(SOURCE_MAP, "htt://<nori ate the tag. yum.>");
129129
let err = SourceMap::from_headers(Url::parse("https://example.net").unwrap(), headers)
130130
.unwrap_err();
131-
assert_eq!(err.status(), 400);
131+
assert_eq!(err.status(), 500);
132132
Ok(())
133133
}
134134

0 commit comments

Comments
 (0)