1 use crate::error::Error;
2 use crate::globals;
3 
4 use fractal_api::reqwest;
5 use gio::prelude::*;
6 
7 use std::sync::Mutex;
8 use std::time::Duration;
9 
10 // Special URI used by gio to indicate no proxy
11 const PROXY_DIRECT_URI: &str = "direct://";
12 
13 #[derive(Debug, Default, Eq, PartialEq)]
14 pub struct ProxySettings {
15     http_proxy: Vec<String>,
16     https_proxy: Vec<String>,
17 }
18 
19 impl ProxySettings {
new<I, T>(http_proxy: T, https_proxy: T) -> Self where I: PartialEq<str> + Into<String>, T: IntoIterator<Item = I>,20     fn new<I, T>(http_proxy: T, https_proxy: T) -> Self
21     where
22         I: PartialEq<str> + Into<String>,
23         T: IntoIterator<Item = I>,
24     {
25         Self {
26             http_proxy: http_proxy
27                 .into_iter()
28                 .filter(|proxy| proxy != PROXY_DIRECT_URI)
29                 .map(Into::into)
30                 .collect(),
31             https_proxy: https_proxy
32                 .into_iter()
33                 .filter(|proxy| proxy != PROXY_DIRECT_URI)
34                 .map(Into::into)
35                 .collect(),
36         }
37     }
38 
current() -> Result<Self, Error>39     pub fn current() -> Result<Self, Error> {
40         Ok(Self::new(
41             PROXY_RESOLVER.with(|resolver| resolver.lookup("http://", gio::NONE_CANCELLABLE))?,
42             PROXY_RESOLVER.with(|resolver| resolver.lookup("https://", gio::NONE_CANCELLABLE))?,
43         ))
44     }
45 
apply_to_client_builder( &self, mut builder: fractal_api::reqwest::blocking::ClientBuilder, ) -> fractal_api::reqwest::blocking::ClientBuilder46     pub fn apply_to_client_builder(
47         &self,
48         mut builder: fractal_api::reqwest::blocking::ClientBuilder,
49     ) -> fractal_api::reqwest::blocking::ClientBuilder {
50         // Reqwest only supports one proxy for each type
51         if let Some(http_proxy) = self
52             .http_proxy
53             .get(0)
54             .map(reqwest::Proxy::http)
55             .and_then(Result::ok)
56         {
57             builder = builder.proxy(http_proxy);
58         }
59         if let Some(https_proxy) = self
60             .https_proxy
61             .get(0)
62             .map(reqwest::Proxy::http)
63             .and_then(Result::ok)
64         {
65             builder = builder.proxy(https_proxy);
66         }
67 
68         builder
69     }
70 }
71 
72 // gio::ProxyResolver can't be sent or shared
73 thread_local! {
74     static PROXY_RESOLVER: gio::ProxyResolver =
75         gio::ProxyResolver::get_default().expect("Couldn't get proxy resolver");
76 }
77 
78 #[derive(Debug)]
79 struct ClientInner {
80     client: fractal_api::reqwest::blocking::Client,
81     proxy_settings: ProxySettings,
82 }
83 
84 #[derive(Debug)]
85 pub struct Client {
86     inner: Mutex<ClientInner>,
87 }
88 
89 impl Client {
new() -> Client90     pub fn new() -> Client {
91         Client {
92             inner: Mutex::new(ClientInner {
93                 client: Self::build(fractal_api::reqwest::blocking::Client::builder()),
94                 proxy_settings: Default::default(),
95             }),
96         }
97     }
98 
get_client(&self) -> fractal_api::reqwest::blocking::Client99     pub fn get_client(&self) -> fractal_api::reqwest::blocking::Client {
100         // Lock first so we don't overwrite proxy settings with outdated information
101         let mut inner = self.inner.lock().unwrap();
102 
103         let new_proxy_settings = ProxySettings::current().unwrap_or_default();
104 
105         if inner.proxy_settings != new_proxy_settings {
106             let mut builder = fractal_api::reqwest::blocking::Client::builder();
107             builder = new_proxy_settings.apply_to_client_builder(builder);
108             let client = Self::build(builder);
109 
110             inner.client = client;
111             inner.proxy_settings = new_proxy_settings;
112         }
113 
114         inner.client.clone()
115     }
116 
build( builder: fractal_api::reqwest::blocking::ClientBuilder, ) -> fractal_api::reqwest::blocking::Client117     fn build(
118         builder: fractal_api::reqwest::blocking::ClientBuilder,
119     ) -> fractal_api::reqwest::blocking::Client {
120         builder
121             .gzip(true)
122             .timeout(Duration::from_secs(globals::TIMEOUT))
123             .build()
124             .expect("Couldn't create a http client")
125     }
126 }
127