1- use std:: env;
1+ use std:: { env, vec } ;
22
33use url:: Url ;
4- use wildmatch:: WildMatch ;
54
65fn get_env ( name : & str ) -> Option < String > {
76 match env:: var ( name. to_ascii_lowercase ( ) ) . or_else ( |_| env:: var ( name. to_ascii_uppercase ( ) ) ) {
@@ -50,7 +49,8 @@ fn get_env_url(name: &str) -> Option<Url> {
5049pub struct ProxySettings {
5150 http_proxy : Option < Url > ,
5251 https_proxy : Option < Url > ,
53- no_proxy_patterns : Vec < WildMatch > ,
52+ disable_proxies : bool ,
53+ no_proxy_hosts : Vec < String > ,
5454}
5555
5656impl ProxySettings {
@@ -61,21 +61,32 @@ impl ProxySettings {
6161
6262 /// Get the proxy configuration from the environment using the `curl`/Unix proxy conventions.
6363 ///
64- /// Only `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY` are supported.
65- /// `NO_PROXY` supports wildcard patterns.
64+ /// Only `ALL_PROXY`, `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY` are supported.
65+ /// Proxies can be disabled on all requests by setting `NO_PROXY` to `*`, similar to `curl`.
66+ /// `HTTP_PROXY` or `HTTPS_PROXY` take precedence over values set by `ALL_PROXY` for their
67+ /// respective schemes.
68+ ///
69+ /// See <https://curl.se/docs/manpage.html#--noproxy>
6670 pub fn from_env ( ) -> ProxySettings {
71+ let all_proxy = get_env_url ( "all_proxy" ) ;
6772 let http_proxy = get_env_url ( "http_proxy" ) ;
6873 let https_proxy = get_env_url ( "https_proxy" ) ;
6974 let no_proxy = get_env ( "no_proxy" ) ;
7075
71- let no_proxy_patterns = no_proxy
72- . map ( |x| x. split ( ',' ) . map ( |pat| WildMatch :: new ( pat. trim ( ) ) ) . collect :: < Vec < _ > > ( ) )
73- . unwrap_or_default ( ) ;
76+ let disable_proxies = no_proxy. as_deref ( ) . unwrap_or ( "" ) == "*" ;
77+ let mut no_proxy_hosts = vec ! [ ] ;
78+
79+ if !disable_proxies {
80+ if let Some ( no_proxy) = no_proxy {
81+ no_proxy_hosts. extend ( no_proxy. split ( ',' ) . map ( |s| s. trim ( ) . to_lowercase ( ) ) ) ;
82+ }
83+ }
7484
7585 ProxySettings {
76- http_proxy,
77- https_proxy,
78- no_proxy_patterns,
86+ http_proxy : http_proxy. or_else ( || all_proxy. clone ( ) ) ,
87+ https_proxy : https_proxy. or ( all_proxy) ,
88+ disable_proxies,
89+ no_proxy_hosts,
7990 }
8091 }
8192
@@ -84,8 +95,12 @@ impl ProxySettings {
8495 /// None is returned if there is no proxy configured for the scheme or if the hostname
8596 /// matches a pattern in the no proxy list.
8697 pub fn for_url ( & self , url : & Url ) -> Option < & Url > {
98+ if self . disable_proxies {
99+ return None ;
100+ }
101+
87102 if let Some ( host) = url. host_str ( ) {
88- if !self . no_proxy_patterns . iter ( ) . any ( |x| x. matches ( host ) ) {
103+ if !self . no_proxy_hosts . iter ( ) . any ( |x| x. to_lowercase ( ) == host ) {
89104 return match url. scheme ( ) {
90105 "http" => self . http_proxy . as_ref ( ) ,
91106 "https" => self . https_proxy . as_ref ( ) ,
@@ -110,7 +125,8 @@ impl ProxySettingsBuilder {
110125 inner : ProxySettings {
111126 http_proxy : None ,
112127 https_proxy : None ,
113- no_proxy_patterns : vec ! [ ] ,
128+ disable_proxies : false ,
129+ no_proxy_hosts : vec ! [ ] ,
114130 } ,
115131 }
116132 }
@@ -135,10 +151,10 @@ impl ProxySettingsBuilder {
135151
136152 /// Add a hostname pattern to ignore when finding the proxy to use for a URL.
137153 ///
138- /// For instance `*. mycompany.local` will make every hostname which ends with `. mycompany.local`
154+ /// For instance `mycompany.local` will make requests with the hostname ` mycompany.local`
139155 /// not go trough the proxy.
140- pub fn add_no_proxy_pattern ( mut self , pattern : impl AsRef < str > ) -> Self {
141- self . inner . no_proxy_patterns . push ( WildMatch :: new ( pattern. as_ref ( ) ) ) ;
156+ pub fn add_no_proxy_host ( mut self , pattern : impl AsRef < str > ) -> Self {
157+ self . inner . no_proxy_hosts . push ( pattern. as_ref ( ) . to_lowercase ( ) ) ;
142158 self
143159 }
144160
@@ -159,7 +175,8 @@ fn test_proxy_for_url() {
159175 let s = ProxySettings {
160176 http_proxy : Some ( "http://proxy1:3128" . parse ( ) . unwrap ( ) ) ,
161177 https_proxy : Some ( "http://proxy2:3128" . parse ( ) . unwrap ( ) ) ,
162- no_proxy_patterns : vec ! [ WildMatch :: new( "*.com" ) ] ,
178+ disable_proxies : false ,
179+ no_proxy_hosts : vec ! [ "reddit.com" . into( ) ] ,
163180 } ;
164181
165182 assert_eq ! (
@@ -174,3 +191,91 @@ fn test_proxy_for_url() {
174191
175192 assert_eq ! ( s. for_url( & Url :: parse( "https://reddit.com" ) . unwrap( ) ) , None ) ;
176193}
194+
195+ #[ test]
196+ fn test_proxy_for_url_disabled ( ) {
197+ let s = ProxySettings {
198+ http_proxy : Some ( "http://proxy1:3128" . parse ( ) . unwrap ( ) ) ,
199+ https_proxy : Some ( "http://proxy2:3128" . parse ( ) . unwrap ( ) ) ,
200+ disable_proxies : true ,
201+ no_proxy_hosts : vec ! [ ] ,
202+ } ;
203+
204+ assert_eq ! ( s. for_url( & Url :: parse( "https://reddit.com" ) . unwrap( ) ) , None ) ;
205+ assert_eq ! ( s. for_url( & Url :: parse( "https://www.google.ca" ) . unwrap( ) ) , None ) ;
206+ }
207+
208+ #[ cfg( test) ]
209+ fn with_reset_proxy_vars < T > ( test : T )
210+ where
211+ T : FnOnce ( ) + std:: panic:: UnwindSafe ,
212+ {
213+ use std:: sync:: Mutex ;
214+
215+ lazy_static:: lazy_static! {
216+ static ref LOCK : Mutex <( ) > = Mutex :: new( ( ) ) ;
217+ } ;
218+
219+ let _guard = LOCK . lock ( ) . unwrap ( ) ;
220+
221+ env:: remove_var ( "ALL_PROXY" ) ;
222+ env:: remove_var ( "HTTP_PROXY" ) ;
223+ env:: remove_var ( "HTTPS_PROXY" ) ;
224+ env:: remove_var ( "NO_PROXY" ) ;
225+
226+ let result = std:: panic:: catch_unwind ( test) ;
227+
228+ // teardown if ever needed
229+
230+ if let Err ( ctx) = result {
231+ std:: panic:: resume_unwind ( ctx) ;
232+ }
233+ }
234+
235+ #[ test]
236+ fn test_proxy_from_env_all_proxy ( ) {
237+ with_reset_proxy_vars ( || {
238+ env:: set_var ( "ALL_PROXY" , "http://proxy:3128" ) ;
239+
240+ let s = ProxySettings :: from_env ( ) ;
241+
242+ assert_eq ! ( s. http_proxy. unwrap( ) . as_str( ) , "http://proxy:3128/" ) ;
243+ assert_eq ! ( s. https_proxy. unwrap( ) . as_str( ) , "http://proxy:3128/" ) ;
244+ } ) ;
245+ }
246+
247+ #[ test]
248+ fn test_proxy_from_env_override ( ) {
249+ with_reset_proxy_vars ( || {
250+ env:: set_var ( "ALL_PROXY" , "http://proxy:3128" ) ;
251+ env:: set_var ( "HTTP_PROXY" , "http://proxy:3129" ) ;
252+ env:: set_var ( "HTTPS_PROXY" , "http://proxy:3130" ) ;
253+
254+ let s = ProxySettings :: from_env ( ) ;
255+
256+ assert_eq ! ( s. http_proxy. unwrap( ) . as_str( ) , "http://proxy:3129/" ) ;
257+ assert_eq ! ( s. https_proxy. unwrap( ) . as_str( ) , "http://proxy:3130/" ) ;
258+ } ) ;
259+ }
260+
261+ #[ test]
262+ fn test_proxy_from_env_no_proxy_wildcard ( ) {
263+ with_reset_proxy_vars ( || {
264+ env:: set_var ( "NO_PROXY" , "*" ) ;
265+
266+ let s = ProxySettings :: from_env ( ) ;
267+
268+ assert ! ( s. disable_proxies) ;
269+ } ) ;
270+ }
271+
272+ #[ test]
273+ fn test_proxy_from_env_no_proxy ( ) {
274+ with_reset_proxy_vars ( || {
275+ env:: set_var ( "NO_PROXY" , "example.com, www.reddit.com, google.ca " ) ;
276+
277+ let s = ProxySettings :: from_env ( ) ;
278+
279+ assert_eq ! ( s. no_proxy_hosts, vec![ "example.com" , "www.reddit.com" , "google.ca" ] ) ;
280+ } ) ;
281+ }
0 commit comments