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