http_types_rs/other/
referer.rs

1use crate::headers::{Header, 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_rs::Result<()> {
21/// #
22/// use http_types_rs::{Response, Url};
23/// use http_types_rs::other::Referer;
24///
25/// let referer = Referer::new(Url::parse("https://example.net/")?);
26///
27/// let mut res = Response::new(200);
28/// res.insert_header(&referer, &referer);
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    /// Get the url.
74    pub fn location(&self) -> &Url {
75        &self.location
76    }
77
78    /// Set the url.
79    pub fn set_location<U>(&mut self, location: U) -> Result<(), U::Error>
80    where
81        U: TryInto<Url>,
82        U::Error: std::fmt::Debug,
83    {
84        self.location = location.try_into()?;
85        Ok(())
86    }
87}
88
89impl Header for Referer {
90    fn header_name(&self) -> HeaderName {
91        REFERER
92    }
93
94    fn header_value(&self) -> HeaderValue {
95        let output = self.location.to_string();
96
97        // SAFETY: the internal string is validated to be ASCII.
98        unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
99    }
100}
101
102#[cfg(test)]
103mod test {
104    use super::*;
105    use crate::headers::Headers;
106
107    #[test]
108    fn smoke() -> crate::Result<()> {
109        let referer = Referer::new(Url::parse("https://example.net/test.json")?);
110
111        let mut headers = Headers::new();
112        referer.apply_header(&mut headers);
113
114        let base_url = Url::parse("https://example.net/")?;
115        let referer = Referer::from_headers(base_url, headers)?.unwrap();
116        assert_eq!(referer.location(), &Url::parse("https://example.net/test.json")?);
117        Ok(())
118    }
119
120    #[test]
121    fn bad_request_on_parse_error() {
122        let mut headers = Headers::new();
123        headers.insert(REFERER, "htt://<nori ate the tag. yum.>").unwrap();
124        let err = Referer::from_headers(Url::parse("https://example.net").unwrap(), headers).unwrap_err();
125        assert_eq!(err.status(), 500);
126    }
127
128    #[test]
129    fn fallback_works() -> crate::Result<()> {
130        let mut headers = Headers::new();
131        headers.insert(REFERER, "/test.json").unwrap();
132
133        let base_url = Url::parse("https://fallback.net/")?;
134        let referer = Referer::from_headers(base_url, headers)?.unwrap();
135        assert_eq!(referer.location(), &Url::parse("https://fallback.net/test.json")?);
136
137        let mut headers = Headers::new();
138        headers.insert(REFERER, "https://example.com/test.json").unwrap();
139
140        let base_url = Url::parse("https://fallback.net/")?;
141        let referer = Referer::from_headers(base_url, headers)?.unwrap();
142        assert_eq!(referer.location(), &Url::parse("https://example.com/test.json")?);
143        Ok(())
144    }
145}