http_types/other/
referer.rs

1use crate::headers::{HeaderName, HeaderValue, Headers, REFERER};
2use crate::{bail_status as bail, Status, Url};
3
4use 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)]
37pub struct Referer {
38    location: Url,
39}
40
41impl 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)]
108mod 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() {
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    }
136
137    #[test]
138    fn fallback_works() -> crate::Result<()> {
139        let mut headers = Headers::new();
140        headers.insert(REFERER, "/test.json");
141
142        let base_url = Url::parse("https://fallback.net/")?;
143        let referer = Referer::from_headers(base_url, headers)?.unwrap();
144        assert_eq!(
145            referer.location(),
146            &Url::parse("https://fallback.net/test.json")?
147        );
148
149        let mut headers = Headers::new();
150        headers.insert(REFERER, "https://example.com/test.json");
151
152        let base_url = Url::parse("https://fallback.net/")?;
153        let referer = Referer::from_headers(base_url, headers)?.unwrap();
154        assert_eq!(
155            referer.location(),
156            &Url::parse("https://example.com/test.json")?
157        );
158        Ok(())
159    }
160}