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 default_user_agent: Option<String>,
27 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
41pub 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#[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#[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
71pub 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 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}