1313//! authentication to be used.
1414
1515use std:: fmt;
16- use std:: net:: IpAddr ;
16+ use std:: net:: { IpAddr , Ipv4Addr , Ipv6Addr } ;
1717
1818use http:: header:: HeaderValue ;
1919use ipnet:: IpNet ;
@@ -50,6 +50,8 @@ pub struct Builder {
5050 http : String ,
5151 https : String ,
5252 no : String ,
53+ no_local : bool ,
54+ no_loopback : bool ,
5355}
5456
5557#[ derive( Clone ) ]
@@ -66,6 +68,8 @@ enum Auth {
6668struct NoProxy {
6769 ips : IpMatcher ,
6870 domains : DomainMatcher ,
71+ local_names : bool ,
72+ loopback_hosts : bool ,
6973}
7074
7175#[ derive( Clone , Debug , Default ) ]
@@ -232,6 +236,8 @@ impl Builder {
232236 http : get_first_env ( & [ "HTTP_PROXY" , "http_proxy" ] ) ,
233237 https : get_first_env ( & [ "HTTPS_PROXY" , "https_proxy" ] ) ,
234238 no : get_first_env ( & [ "NO_PROXY" , "no_proxy" ] ) ,
239+ no_local : false ,
240+ no_loopback : false ,
235241 }
236242 }
237243
@@ -315,7 +321,9 @@ impl Builder {
315321 Matcher {
316322 http : parse_env_uri ( & self . http ) . or_else ( || all. clone ( ) ) ,
317323 https : parse_env_uri ( & self . https ) . or ( all) ,
318- no : NoProxy :: from_string ( & self . no ) ,
324+ no : NoProxy :: from_string ( & self . no )
325+ . with_local_names ( self . no_local )
326+ . with_loopback_hosts ( self . no_loopback ) ,
319327 }
320328 }
321329}
@@ -420,6 +428,8 @@ impl NoProxy {
420428 NoProxy {
421429 ips : IpMatcher ( Vec :: new ( ) ) ,
422430 domains : DomainMatcher ( Vec :: new ( ) ) ,
431+ local_names : false ,
432+ loopback_hosts : false ,
423433 }
424434 }
425435
@@ -463,6 +473,8 @@ impl NoProxy {
463473 NoProxy {
464474 ips : IpMatcher ( ips) ,
465475 domains : DomainMatcher ( domains) ,
476+ local_names : false ,
477+ loopback_hosts : false ,
466478 }
467479 }
468480
@@ -478,16 +490,41 @@ impl NoProxy {
478490 } ;
479491 match host. parse :: < IpAddr > ( ) {
480492 // If we can parse an IP addr, then use it, otherwise, assume it is a domain
481- Ok ( ip) => self . ips . contains ( ip) ,
482- Err ( _) => self . domains . contains ( host) ,
493+ Ok ( ip) => self . ips . contains ( ip) || self . loopback_hosts && is_loopback_ip ( ip) ,
494+ Err ( _) => {
495+ self . loopback_hosts && is_loopback_name ( host)
496+ || self . local_names && !host. contains ( '.' )
497+ || self . domains . contains ( host)
498+ }
483499 }
484500 }
485501
486502 fn is_empty ( & self ) -> bool {
487- self . ips . 0 . is_empty ( ) && self . domains . 0 . is_empty ( )
503+ self . ips . 0 . is_empty ( )
504+ && self . domains . 0 . is_empty ( )
505+ && !self . local_names
506+ && !self . loopback_hosts
507+ }
508+
509+ fn with_local_names ( mut self , local_names : bool ) -> Self {
510+ self . local_names = local_names;
511+ self
512+ }
513+
514+ fn with_loopback_hosts ( mut self , loopback_hosts : bool ) -> Self {
515+ self . loopback_hosts = loopback_hosts;
516+ self
488517 }
489518}
490519
520+ fn is_loopback_ip ( ip : IpAddr ) -> bool {
521+ ip == IpAddr :: V4 ( Ipv4Addr :: LOCALHOST ) || ip == IpAddr :: V6 ( Ipv6Addr :: LOCALHOST )
522+ }
523+
524+ fn is_loopback_name ( host : & str ) -> bool {
525+ host. eq_ignore_ascii_case ( "localhost" ) || host. eq_ignore_ascii_case ( "loopback" )
526+ }
527+
491528impl IpMatcher {
492529 fn contains ( & self , addr : IpAddr ) -> bool {
493530 for ip in & self . 0 {
@@ -662,6 +699,12 @@ mod mac {
662699#[ cfg( feature = "client-proxy-system" ) ]
663700#[ cfg( windows) ]
664701mod win {
702+ struct ProxyOverride {
703+ no : String ,
704+ no_local : bool ,
705+ no_loopback : bool ,
706+ }
707+
665708 pub ( super ) fn with_system ( builder : & mut super :: Builder ) {
666709 let settings = if let Ok ( settings) = windows_registry:: CURRENT_USER
667710 . open ( "Software\\ Microsoft\\ Windows\\ CurrentVersion\\ Internet Settings" )
@@ -685,16 +728,59 @@ mod win {
685728 }
686729
687730 if builder. no . is_empty ( ) {
731+ builder. no_loopback = true ;
732+
688733 if let Ok ( val) = settings. get_string ( "ProxyOverride" ) {
689- builder. no = val
690- . split ( ';' )
691- . map ( |s| s. trim ( ) )
692- . collect :: < Vec < & str > > ( )
693- . join ( "," )
694- . replace ( "*." , "" ) ;
734+ let proxy_override = parse_proxy_override ( & val) ;
735+ builder. no = proxy_override. no ;
736+ builder. no_local = proxy_override. no_local ;
737+ builder. no_loopback = proxy_override. no_loopback ;
695738 }
696739 }
697740 }
741+
742+ fn parse_proxy_override ( val : & str ) -> ProxyOverride {
743+ let mut no_local = false ;
744+ let mut no_loopback = true ;
745+ let no = val
746+ . split ( ';' )
747+ . map ( str:: trim)
748+ . filter_map ( |part| {
749+ if part. is_empty ( ) {
750+ None
751+ } else if part. eq_ignore_ascii_case ( "<local>" ) {
752+ no_local = true ;
753+ None
754+ } else if part. eq_ignore_ascii_case ( "<-loopback>" ) {
755+ no_loopback = false ;
756+ None
757+ } else {
758+ Some ( part)
759+ }
760+ } )
761+ . collect :: < Vec < & str > > ( )
762+ . join ( "," )
763+ . replace ( "*." , "" ) ;
764+
765+ ProxyOverride {
766+ no,
767+ no_local,
768+ no_loopback,
769+ }
770+ }
771+
772+ #[ cfg( test) ]
773+ mod tests {
774+ #[ test]
775+ fn test_parse_proxy_override_macros ( ) {
776+ let rules =
777+ super :: parse_proxy_override ( "*.example.com; <local>; <-loopback>; 10.0.0.1 ;" ) ;
778+
779+ assert_eq ! ( rules. no, "example.com,10.0.0.1" ) ;
780+ assert ! ( rules. no_local) ;
781+ assert ! ( !rules. no_loopback) ;
782+ }
783+ }
698784}
699785
700786#[ cfg( test) ]
@@ -926,4 +1012,53 @@ mod tests {
9261012 . intercept( & "http://Www.Example.Com" . parse( ) . unwrap( ) )
9271013 . is_none( ) ) ;
9281014 }
1015+
1016+ #[ test]
1017+ fn test_no_proxy_local_names ( ) {
1018+ let mut builder = Matcher :: builder ( ) ;
1019+ builder. all = "http://proxy.local" . into ( ) ;
1020+ builder. no_local = true ;
1021+ let p = builder. build ( ) ;
1022+
1023+ assert ! ( p. intercept( & "http://webserver" . parse( ) . unwrap( ) ) . is_none( ) ) ;
1024+ assert ! ( p. intercept( & "http://INTRANET" . parse( ) . unwrap( ) ) . is_none( ) ) ;
1025+
1026+ assert ! ( p
1027+ . intercept( & "http://webserver.example.com" . parse( ) . unwrap( ) )
1028+ . is_some( ) ) ;
1029+ assert ! ( p. intercept( & "http://10.0.0.1" . parse( ) . unwrap( ) ) . is_some( ) ) ;
1030+ assert ! ( p. intercept( & "http://[::1]" . parse( ) . unwrap( ) ) . is_some( ) ) ;
1031+ }
1032+
1033+ #[ test]
1034+ fn test_no_proxy_loopback_hosts ( ) {
1035+ let mut builder = Matcher :: builder ( ) ;
1036+ builder. all = "http://proxy.local" . into ( ) ;
1037+ builder. no_loopback = true ;
1038+ let p = builder. build ( ) ;
1039+
1040+ assert ! ( p. intercept( & "http://127.0.0.1" . parse( ) . unwrap( ) ) . is_none( ) ) ;
1041+ assert ! ( p. intercept( & "http://[::1]" . parse( ) . unwrap( ) ) . is_none( ) ) ;
1042+ assert ! ( p. intercept( & "http://localhost" . parse( ) . unwrap( ) ) . is_none( ) ) ;
1043+ assert ! ( p. intercept( & "http://LOCALHOST" . parse( ) . unwrap( ) ) . is_none( ) ) ;
1044+ assert ! ( p. intercept( & "http://loopback" . parse( ) . unwrap( ) ) . is_none( ) ) ;
1045+ assert ! ( p. intercept( & "http://LOOPBACK" . parse( ) . unwrap( ) ) . is_none( ) ) ;
1046+
1047+ assert ! ( p. intercept( & "http://webserver" . parse( ) . unwrap( ) ) . is_some( ) ) ;
1048+ assert ! ( p. intercept( & "http://10.0.0.1" . parse( ) . unwrap( ) ) . is_some( ) ) ;
1049+ }
1050+
1051+ #[ test]
1052+ fn test_no_proxy_loopback_hosts_disabled ( ) {
1053+ let mut builder = Matcher :: builder ( ) ;
1054+ builder. all = "http://proxy.local" . into ( ) ;
1055+ let p = builder. build ( ) ;
1056+
1057+ assert ! ( p. intercept( & "http://127.0.0.1" . parse( ) . unwrap( ) ) . is_some( ) ) ;
1058+ assert ! ( p. intercept( & "http://[::1]" . parse( ) . unwrap( ) ) . is_some( ) ) ;
1059+ assert ! ( p. intercept( & "http://localhost" . parse( ) . unwrap( ) ) . is_some( ) ) ;
1060+ assert ! ( p. intercept( & "http://LOCALHOST" . parse( ) . unwrap( ) ) . is_some( ) ) ;
1061+ assert ! ( p. intercept( & "http://loopback" . parse( ) . unwrap( ) ) . is_some( ) ) ;
1062+ assert ! ( p. intercept( & "http://LOOPBACK" . parse( ) . unwrap( ) ) . is_some( ) ) ;
1063+ }
9291064}
0 commit comments