viaduct/
settings.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use once_cell::sync::Lazy;
6use parking_lot::RwLock;
7use url::Url;
8
9/// Note: reqwest allows these only to be specified per-Client. concept-fetch
10/// allows these to be specified on each call to fetch. I think it's worth
11/// keeping a single global reqwest::Client in the reqwest backend, to simplify
12/// the way we abstract away from these.
13///
14/// In the future, should we need it, we might be able to add a CustomClient type
15/// with custom settings. In the reqwest backend this would store a Client, and
16/// in the concept-fetch backend it would only store the settings, and populate
17/// things on the fly.
18#[derive(Debug)]
19#[non_exhaustive]
20pub struct Settings {
21    pub default_user_agent: Option<String>,
22    pub addn_allowed_insecure_url: Option<Url>,
23}
24
25// The singleton instance of our settings.
26pub static GLOBAL_SETTINGS: Lazy<RwLock<Settings>> = Lazy::new(|| {
27    RwLock::new(Settings {
28        default_user_agent: None,
29        addn_allowed_insecure_url: None,
30    })
31});
32
33/// Allow non-HTTPS requests to the emulator loopback URL
34#[uniffi::export]
35pub fn allow_android_emulator_loopback() {
36    let url = url::Url::parse("http://10.0.2.2").unwrap();
37    let mut settings = GLOBAL_SETTINGS.write();
38    settings.addn_allowed_insecure_url = Some(url);
39}
40
41/// Set the global default user-agent
42///
43/// This is what's used when no user-agent is set in the `ClientSettings` and no `user-agent`
44/// header is set in the Request.
45#[uniffi::export]
46pub fn set_global_default_user_agent(user_agent: String) {
47    let mut settings = GLOBAL_SETTINGS.write();
48    settings.default_user_agent = Some(user_agent);
49}
50
51/// Validate a request, respecting the `addn_allowed_insecure_url` setting.
52pub fn validate_request(request: &crate::Request) -> Result<(), crate::ViaductError> {
53    if request.url.scheme() != "https"
54        && match request.url.host() {
55            Some(url::Host::Domain(d)) => d != "localhost",
56            Some(url::Host::Ipv4(addr)) => !addr.is_loopback(),
57            Some(url::Host::Ipv6(addr)) => !addr.is_loopback(),
58            None => true,
59        }
60        && {
61            let settings = GLOBAL_SETTINGS.read();
62            settings
63                .addn_allowed_insecure_url
64                .as_ref()
65                .map(|url| url.host() != request.url.host() || url.scheme() != request.url.scheme())
66                .unwrap_or(true)
67        }
68    {
69        return Err(crate::ViaductError::NonTlsUrl);
70    }
71    Ok(())
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn test_validate_request() {
80        let _https_request = crate::Request::new(
81            crate::Method::Get,
82            url::Url::parse("https://www.example.com").unwrap(),
83        );
84        assert!(validate_request(&_https_request).is_ok());
85
86        let _http_request = crate::Request::new(
87            crate::Method::Get,
88            url::Url::parse("http://www.example.com").unwrap(),
89        );
90        assert!(validate_request(&_http_request).is_err());
91
92        let _localhost_https_request = crate::Request::new(
93            crate::Method::Get,
94            url::Url::parse("https://127.0.0.1/index.html").unwrap(),
95        );
96        assert!(validate_request(&_localhost_https_request).is_ok());
97
98        let _localhost_https_request_2 = crate::Request::new(
99            crate::Method::Get,
100            url::Url::parse("https://localhost:4242/").unwrap(),
101        );
102        assert!(validate_request(&_localhost_https_request_2).is_ok());
103
104        let _localhost_http_request = crate::Request::new(
105            crate::Method::Get,
106            url::Url::parse("http://localhost:4242/").unwrap(),
107        );
108        assert!(validate_request(&_localhost_http_request).is_ok());
109
110        let localhost_request = crate::Request::new(
111            crate::Method::Get,
112            url::Url::parse("localhost:4242/").unwrap(),
113        );
114        assert!(validate_request(&localhost_request).is_err());
115
116        let localhost_request_shorthand_ipv6 =
117            crate::Request::new(crate::Method::Get, url::Url::parse("http://[::1]").unwrap());
118        assert!(validate_request(&localhost_request_shorthand_ipv6).is_ok());
119
120        let localhost_request_ipv6 = crate::Request::new(
121            crate::Method::Get,
122            url::Url::parse("http://[0:0:0:0:0:0:0:1]").unwrap(),
123        );
124        assert!(validate_request(&localhost_request_ipv6).is_ok());
125    }
126
127    #[test]
128    fn test_validate_request_addn_allowed_insecure_url() {
129        let request_root = crate::Request::new(
130            crate::Method::Get,
131            url::Url::parse("http://anything").unwrap(),
132        );
133        let request = crate::Request::new(
134            crate::Method::Get,
135            url::Url::parse("http://anything/path").unwrap(),
136        );
137        // This should never be accepted.
138        let request_ftp = crate::Request::new(
139            crate::Method::Get,
140            url::Url::parse("ftp://anything/path").unwrap(),
141        );
142        assert!(validate_request(&request_root).is_err());
143        assert!(validate_request(&request).is_err());
144        {
145            let mut settings = GLOBAL_SETTINGS.write();
146            settings.addn_allowed_insecure_url =
147                Some(url::Url::parse("http://something-else").unwrap());
148        }
149        assert!(validate_request(&request_root).is_err());
150        assert!(validate_request(&request).is_err());
151
152        {
153            let mut settings = GLOBAL_SETTINGS.write();
154            settings.addn_allowed_insecure_url = Some(url::Url::parse("http://anything").unwrap());
155        }
156        assert!(validate_request(&request_root).is_ok());
157        assert!(validate_request(&request).is_ok());
158        assert!(validate_request(&request_ftp).is_err());
159    }
160}