viaduct/
backend.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 crate::GLOBAL_SETTINGS;
6use crate::{info, trace};
7use ffi::FfiBackend;
8use once_cell::sync::OnceCell;
9mod ffi;
10
11pub fn note_backend(which: &str) {
12    // If trace logs are enabled: log on every request. Otherwise, just log on
13    // the first request at `info` level. We remember if the Once was triggered
14    // to avoid logging twice in the first case.
15    static NOTE_BACKEND_ONCE: std::sync::Once = std::sync::Once::new();
16    let mut called = false;
17    NOTE_BACKEND_ONCE.call_once(|| {
18        info!("Using HTTP backend {}", which);
19        called = true;
20    });
21    if !called {
22        trace!("Using HTTP backend {}", which);
23    }
24}
25
26pub trait Backend: Send + Sync + 'static {
27    fn send(&self, request: crate::Request) -> Result<crate::Response, crate::Error>;
28}
29
30static BACKEND: OnceCell<&'static dyn Backend> = OnceCell::new();
31
32pub fn set_backend(b: &'static dyn Backend) -> Result<(), crate::Error> {
33    BACKEND
34        .set(b)
35        .map_err(|_| crate::error::Error::SetBackendError)
36}
37
38pub(crate) fn get_backend() -> &'static dyn Backend {
39    *BACKEND.get_or_init(|| Box::leak(Box::new(FfiBackend)))
40}
41
42pub fn send(request: crate::Request) -> Result<crate::Response, crate::Error> {
43    validate_request(&request)?;
44    get_backend().send(request)
45}
46
47pub fn validate_request(request: &crate::Request) -> Result<(), crate::Error> {
48    if request.url.scheme() != "https"
49        && match request.url.host() {
50            Some(url::Host::Domain(d)) => d != "localhost",
51            Some(url::Host::Ipv4(addr)) => !addr.is_loopback(),
52            Some(url::Host::Ipv6(addr)) => !addr.is_loopback(),
53            None => true,
54        }
55        && {
56            let settings = GLOBAL_SETTINGS.read();
57            settings
58                .addn_allowed_insecure_url
59                .as_ref()
60                .map(|url| url.host() != request.url.host() || url.scheme() != request.url.scheme())
61                .unwrap_or(true)
62        }
63    {
64        return Err(crate::Error::NonTlsUrl);
65    }
66    Ok(())
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn test_validate_request() {
75        let _https_request = crate::Request::new(
76            crate::Method::Get,
77            url::Url::parse("https://www.example.com").unwrap(),
78        );
79        assert!(validate_request(&_https_request).is_ok());
80
81        let _http_request = crate::Request::new(
82            crate::Method::Get,
83            url::Url::parse("http://www.example.com").unwrap(),
84        );
85        assert!(validate_request(&_http_request).is_err());
86
87        let _localhost_https_request = crate::Request::new(
88            crate::Method::Get,
89            url::Url::parse("https://127.0.0.1/index.html").unwrap(),
90        );
91        assert!(validate_request(&_localhost_https_request).is_ok());
92
93        let _localhost_https_request_2 = crate::Request::new(
94            crate::Method::Get,
95            url::Url::parse("https://localhost:4242/").unwrap(),
96        );
97        assert!(validate_request(&_localhost_https_request_2).is_ok());
98
99        let _localhost_http_request = crate::Request::new(
100            crate::Method::Get,
101            url::Url::parse("http://localhost:4242/").unwrap(),
102        );
103        assert!(validate_request(&_localhost_http_request).is_ok());
104
105        let localhost_request = crate::Request::new(
106            crate::Method::Get,
107            url::Url::parse("localhost:4242/").unwrap(),
108        );
109        assert!(validate_request(&localhost_request).is_err());
110
111        let localhost_request_shorthand_ipv6 =
112            crate::Request::new(crate::Method::Get, url::Url::parse("http://[::1]").unwrap());
113        assert!(validate_request(&localhost_request_shorthand_ipv6).is_ok());
114
115        let localhost_request_ipv6 = crate::Request::new(
116            crate::Method::Get,
117            url::Url::parse("http://[0:0:0:0:0:0:0:1]").unwrap(),
118        );
119        assert!(validate_request(&localhost_request_ipv6).is_ok());
120    }
121
122    #[test]
123    fn test_validate_request_addn_allowed_insecure_url() {
124        let request_root = crate::Request::new(
125            crate::Method::Get,
126            url::Url::parse("http://anything").unwrap(),
127        );
128        let request = crate::Request::new(
129            crate::Method::Get,
130            url::Url::parse("http://anything/path").unwrap(),
131        );
132        // This should never be accepted.
133        let request_ftp = crate::Request::new(
134            crate::Method::Get,
135            url::Url::parse("ftp://anything/path").unwrap(),
136        );
137        assert!(validate_request(&request_root).is_err());
138        assert!(validate_request(&request).is_err());
139        {
140            let mut settings = GLOBAL_SETTINGS.write();
141            settings.addn_allowed_insecure_url =
142                Some(url::Url::parse("http://something-else").unwrap());
143        }
144        assert!(validate_request(&request_root).is_err());
145        assert!(validate_request(&request).is_err());
146
147        {
148            let mut settings = GLOBAL_SETTINGS.write();
149            settings.addn_allowed_insecure_url = Some(url::Url::parse("http://anything").unwrap());
150        }
151        assert!(validate_request(&request_root).is_ok());
152        assert!(validate_request(&request).is_ok());
153        assert!(validate_request(&request_ftp).is_err());
154    }
155}