1use once_cell::sync::Lazy;
6use parking_lot::RwLock;
7use std::time::Duration;
8use url::Url;
9
10#[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 addn_allowed_insecure_url: Option<Url>,
32}
33
34#[cfg(target_os = "ios")]
35const TIMEOUT_DURATION: Duration = Duration::from_secs(7);
36
37#[cfg(not(target_os = "ios"))]
38const TIMEOUT_DURATION: Duration = Duration::from_secs(10);
39
40pub static GLOBAL_SETTINGS: Lazy<RwLock<Settings>> = Lazy::new(|| {
42 RwLock::new(Settings {
43 read_timeout: Some(TIMEOUT_DURATION),
44 connect_timeout: Some(TIMEOUT_DURATION),
45 follow_redirects: true,
46 use_caches: false,
47 addn_allowed_insecure_url: None,
48 })
49});
50
51#[uniffi::export]
53pub fn allow_android_emulator_loopback() {
54 let url = url::Url::parse("http://10.0.2.2").unwrap();
55 let mut settings = GLOBAL_SETTINGS.write();
56 settings.addn_allowed_insecure_url = Some(url);
57}
58
59pub fn validate_request(request: &crate::Request) -> Result<(), crate::ViaductError> {
61 if request.url.scheme() != "https"
62 && match request.url.host() {
63 Some(url::Host::Domain(d)) => d != "localhost",
64 Some(url::Host::Ipv4(addr)) => !addr.is_loopback(),
65 Some(url::Host::Ipv6(addr)) => !addr.is_loopback(),
66 None => true,
67 }
68 && {
69 let settings = GLOBAL_SETTINGS.read();
70 settings
71 .addn_allowed_insecure_url
72 .as_ref()
73 .map(|url| url.host() != request.url.host() || url.scheme() != request.url.scheme())
74 .unwrap_or(true)
75 }
76 {
77 return Err(crate::ViaductError::NonTlsUrl);
78 }
79 Ok(())
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn test_validate_request() {
88 let _https_request = crate::Request::new(
89 crate::Method::Get,
90 url::Url::parse("https://www.example.com").unwrap(),
91 );
92 assert!(validate_request(&_https_request).is_ok());
93
94 let _http_request = crate::Request::new(
95 crate::Method::Get,
96 url::Url::parse("http://www.example.com").unwrap(),
97 );
98 assert!(validate_request(&_http_request).is_err());
99
100 let _localhost_https_request = crate::Request::new(
101 crate::Method::Get,
102 url::Url::parse("https://127.0.0.1/index.html").unwrap(),
103 );
104 assert!(validate_request(&_localhost_https_request).is_ok());
105
106 let _localhost_https_request_2 = crate::Request::new(
107 crate::Method::Get,
108 url::Url::parse("https://localhost:4242/").unwrap(),
109 );
110 assert!(validate_request(&_localhost_https_request_2).is_ok());
111
112 let _localhost_http_request = crate::Request::new(
113 crate::Method::Get,
114 url::Url::parse("http://localhost:4242/").unwrap(),
115 );
116 assert!(validate_request(&_localhost_http_request).is_ok());
117
118 let localhost_request = crate::Request::new(
119 crate::Method::Get,
120 url::Url::parse("localhost:4242/").unwrap(),
121 );
122 assert!(validate_request(&localhost_request).is_err());
123
124 let localhost_request_shorthand_ipv6 =
125 crate::Request::new(crate::Method::Get, url::Url::parse("http://[::1]").unwrap());
126 assert!(validate_request(&localhost_request_shorthand_ipv6).is_ok());
127
128 let localhost_request_ipv6 = crate::Request::new(
129 crate::Method::Get,
130 url::Url::parse("http://[0:0:0:0:0:0:0:1]").unwrap(),
131 );
132 assert!(validate_request(&localhost_request_ipv6).is_ok());
133 }
134
135 #[test]
136 fn test_validate_request_addn_allowed_insecure_url() {
137 let request_root = crate::Request::new(
138 crate::Method::Get,
139 url::Url::parse("http://anything").unwrap(),
140 );
141 let request = crate::Request::new(
142 crate::Method::Get,
143 url::Url::parse("http://anything/path").unwrap(),
144 );
145 let request_ftp = crate::Request::new(
147 crate::Method::Get,
148 url::Url::parse("ftp://anything/path").unwrap(),
149 );
150 assert!(validate_request(&request_root).is_err());
151 assert!(validate_request(&request).is_err());
152 {
153 let mut settings = GLOBAL_SETTINGS.write();
154 settings.addn_allowed_insecure_url =
155 Some(url::Url::parse("http://something-else").unwrap());
156 }
157 assert!(validate_request(&request_root).is_err());
158 assert!(validate_request(&request).is_err());
159
160 {
161 let mut settings = GLOBAL_SETTINGS.write();
162 settings.addn_allowed_insecure_url = Some(url::Url::parse("http://anything").unwrap());
163 }
164 assert!(validate_request(&request_root).is_ok());
165 assert!(validate_request(&request).is_ok());
166 assert!(validate_request(&request_ftp).is_err());
167 }
168}