66using System . Collections . Generic ;
77using System . Diagnostics ;
88using System . Diagnostics . CodeAnalysis ;
9- using System . IO . Compression ;
109using System . Net . NetworkInformation ;
1110using System . Runtime . InteropServices ;
1211using System . Text ;
1312using System . Threading ;
13+ using Microsoft . Win32 ;
1414using SafeWinHttpHandle = Interop . WinHttp . SafeWinHttpHandle ;
1515
1616namespace System . Net . Http
1717{
1818 internal sealed class HttpWindowsProxy : IMultiWebProxy , IDisposable
1919 {
20- private readonly MultiProxy _insecureProxy ; // URI of the http system proxy if set
21- private readonly MultiProxy _secureProxy ; // URI of the https system proxy if set
22- private readonly FailedProxyCache _failedProxies = new FailedProxyCache ( ) ;
23- private readonly List < string > ? _bypass ; // list of domains not to proxy
24- private readonly bool _bypassLocal ; // we should bypass domain considered local
25- private readonly List < IPAddress > ? _localIp ;
20+ private readonly RegistryKey ? _internetSettingsRegistry = Registry . CurrentUser ? . OpenSubKey ( "Software \\ Microsoft \\ Windows \\ CurrentVersion \\ Internet Settings" ) ;
21+ private MultiProxy _insecureProxy ; // URI of the http system proxy if set
22+ private MultiProxy _secureProxy ; // URI of the https system proxy if set
23+ private FailedProxyCache _failedProxies = new FailedProxyCache ( ) ;
24+ private List < string > ? _bypass ; // list of domains not to proxy
25+ private List < IPAddress > ? _localIp ;
2626 private ICredentials ? _credentials ;
27- private readonly WinInetProxyHelper _proxyHelper ;
27+ private WinInetProxyHelper _proxyHelper ;
2828 private SafeWinHttpHandle ? _sessionHandle ;
2929 private bool _disposed ;
30+ private EventWaitHandle _waitHandle = new EventWaitHandle ( false , EventResetMode . AutoReset ) ;
31+ private const int RegistrationFlags = Interop . Advapi32 . REG_NOTIFY_CHANGE_NAME | Interop . Advapi32 . REG_NOTIFY_CHANGE_LAST_SET | Interop . Advapi32 . REG_NOTIFY_CHANGE_ATTRIBUTES | Interop . Advapi32 . REG_NOTIFY_THREAD_AGNOSTIC ;
32+ private RegisteredWaitHandle ? _registeredWaitHandle ;
3033
31- public static bool TryCreate ( [ NotNullWhen ( true ) ] out IWebProxy ? proxy )
34+ // 'proxy' used from tests via Reflection
35+ public HttpWindowsProxy ( WinInetProxyHelper ? proxy = null )
3236 {
33- // This will get basic proxy setting from system using existing
34- // WinInetProxyHelper functions. If no proxy is enabled, it will return null.
35- SafeWinHttpHandle ? sessionHandle = null ;
36- proxy = null ;
3737
38- WinInetProxyHelper proxyHelper = new WinInetProxyHelper ( ) ;
39- if ( ! proxyHelper . ManualSettingsOnly && ! proxyHelper . AutoSettingsUsed )
38+ if ( _internetSettingsRegistry != null && proxy == null )
4039 {
41- return false ;
40+ // we register for change notifications so we can react to changes during lifetime.
41+ if ( Interop . Advapi32 . RegNotifyChangeKeyValue ( _internetSettingsRegistry . Handle , true , RegistrationFlags , _waitHandle . SafeWaitHandle , true ) == 0 )
42+ {
43+ _registeredWaitHandle = ThreadPool . RegisterWaitForSingleObject ( _waitHandle , RegistryChangeNotificationCallback , this , - 1 , false ) ;
44+ }
45+ }
46+
47+ UpdateConfiguration ( proxy ) ;
48+ }
49+
50+ private static void RegistryChangeNotificationCallback ( object ? state , bool timedOut )
51+ {
52+ HttpWindowsProxy proxy = ( HttpWindowsProxy ) state ! ;
53+ if ( ! proxy . _disposed )
54+ {
55+
56+ // This is executed from threadpool. we should not ever throw here.
57+ try
58+ {
59+ // We need to register for notification every time. We regisrerand lock before we process configuration
60+ // so if there is update it would be serialized to ensure consistency.
61+ Interop . Advapi32 . RegNotifyChangeKeyValue ( proxy . _internetSettingsRegistry ! . Handle , true , RegistrationFlags , proxy . _waitHandle . SafeWaitHandle , true ) ;
62+ lock ( proxy )
63+ {
64+ proxy . UpdateConfiguration ( ) ;
65+ }
66+ }
67+ catch ( Exception ex )
68+ {
69+ if ( NetEventSource . Log . IsEnabled ( ) ) NetEventSource . Error ( proxy , $ "Failed to refresh proxy configuration: { ex . Message } ") ;
70+ }
4271 }
72+ }
73+
74+ [ MemberNotNull ( nameof ( _proxyHelper ) ) ]
75+ private void UpdateConfiguration ( WinInetProxyHelper ? proxyHelper = null )
76+ {
77+
78+ proxyHelper ??= new WinInetProxyHelper ( ) ;
4379
4480 if ( proxyHelper . AutoSettingsUsed )
4581 {
4682 if ( NetEventSource . Log . IsEnabled ( ) ) NetEventSource . Info ( proxyHelper , $ "AutoSettingsUsed, calling { nameof ( Interop . WinHttp . WinHttpOpen ) } ") ;
47- sessionHandle = Interop . WinHttp . WinHttpOpen (
83+ SafeWinHttpHandle ? sessionHandle = Interop . WinHttp . WinHttpOpen (
4884 IntPtr . Zero ,
4985 Interop . WinHttp . WINHTTP_ACCESS_TYPE_NO_PROXY ,
5086 Interop . WinHttp . WINHTTP_NO_PROXY_NAME ,
@@ -56,18 +92,10 @@ public static bool TryCreate([NotNullWhen(true)] out IWebProxy? proxy)
5692 // Proxy failures are currently ignored by managed handler.
5793 if ( NetEventSource . Log . IsEnabled ( ) ) NetEventSource . Error ( proxyHelper , $ "{ nameof ( Interop . WinHttp . WinHttpOpen ) } returned invalid handle") ;
5894 sessionHandle . Dispose ( ) ;
59- return false ;
6095 }
61- }
62-
63- proxy = new HttpWindowsProxy ( proxyHelper , sessionHandle ) ;
64- return true ;
65- }
6696
67- private HttpWindowsProxy ( WinInetProxyHelper proxyHelper , SafeWinHttpHandle ? sessionHandle )
68- {
69- _proxyHelper = proxyHelper ;
70- _sessionHandle = sessionHandle ;
97+ _sessionHandle = sessionHandle ;
98+ }
7199
72100 if ( proxyHelper . ManualSettingsUsed )
73101 {
@@ -80,10 +108,12 @@ private HttpWindowsProxy(WinInetProxyHelper proxyHelper, SafeWinHttpHandle? sess
80108 {
81109 int idx = 0 ;
82110 string ? tmp ;
111+ bool bypassLocal = false ;
112+ List < IPAddress > ? localIp = null ;
83113
84114 // Process bypass list for manual setting.
85115 // Initial list size is best guess based on string length assuming each entry is at least 5 characters on average.
86- _bypass = new List < string > ( proxyHelper . ProxyBypass . Length / 5 ) ;
116+ List < string > ? bypass = new List < string > ( proxyHelper . ProxyBypass . Length / 5 ) ;
87117
88118 while ( idx < proxyHelper . ProxyBypass . Length )
89119 {
@@ -114,7 +144,7 @@ private HttpWindowsProxy(WinInetProxyHelper proxyHelper, SafeWinHttpHandle? sess
114144 }
115145 else if ( string . Compare ( proxyHelper . ProxyBypass , start , "<local>" , 0 , 7 , StringComparison . OrdinalIgnoreCase ) == 0 )
116146 {
117- _bypassLocal = true ;
147+ bypassLocal = true ;
118148 tmp = null ;
119149 }
120150 else
@@ -137,28 +167,29 @@ private HttpWindowsProxy(WinInetProxyHelper proxyHelper, SafeWinHttpHandle? sess
137167 continue ;
138168 }
139169
140- _bypass . Add ( tmp ) ;
141- }
142- if ( _bypass . Count == 0 )
143- {
144- // Bypass string only had garbage we did not parse.
145- _bypass = null ;
170+ bypass . Add ( tmp ) ;
146171 }
147- }
148172
149- if ( _bypassLocal )
150- {
151- _localIp = new List < IPAddress > ( ) ;
152- foreach ( NetworkInterface netInterface in NetworkInterface . GetAllNetworkInterfaces ( ) )
173+ _bypass = bypass . Count > 0 ? bypass : null ;
174+
175+ if ( bypassLocal )
153176 {
154- IPInterfaceProperties ipProps = netInterface . GetIPProperties ( ) ;
155- foreach ( UnicastIPAddressInformation addr in ipProps . UnicastAddresses )
177+ localIp = new List < IPAddress > ( ) ;
178+ foreach ( NetworkInterface netInterface in NetworkInterface . GetAllNetworkInterfaces ( ) )
156179 {
157- _localIp . Add ( addr . Address ) ;
180+ IPInterfaceProperties ipProps = netInterface . GetIPProperties ( ) ;
181+ foreach ( UnicastIPAddressInformation addr in ipProps . UnicastAddresses )
182+ {
183+ localIp . Add ( addr . Address ) ;
184+ }
158185 }
159186 }
187+
188+ _localIp = localIp ? . Count > 0 ? localIp : null ;
160189 }
161190 }
191+
192+ _proxyHelper = proxyHelper ;
162193 }
163194
164195 public void Dispose ( )
@@ -171,6 +202,10 @@ public void Dispose()
171202 {
172203 SafeWinHttpHandle . DisposeAndClearHandle ( ref _sessionHandle ) ;
173204 }
205+
206+ _waitHandle ? . Dispose ( ) ;
207+ _internetSettingsRegistry ? . Dispose ( ) ;
208+ _registeredWaitHandle ? . Unregister ( null ) ;
174209 }
175210 }
176211
@@ -179,6 +214,11 @@ public void Dispose()
179214 /// </summary>
180215 public Uri ? GetProxy ( Uri uri )
181216 {
217+ if ( ! _proxyHelper . AutoSettingsUsed && ! _proxyHelper . ManualSettingsOnly )
218+ {
219+ return null ;
220+ }
221+
182222 GetMultiProxy ( uri ) . ReadNext ( out Uri ? proxyUri , out _ ) ;
183223 return proxyUri ;
184224 }
@@ -240,7 +280,7 @@ public MultiProxy GetMultiProxy(Uri uri)
240280 // Fallback to manual settings if present.
241281 if ( _proxyHelper . ManualSettingsUsed )
242282 {
243- if ( _bypassLocal )
283+ if ( _localIp != null )
244284 {
245285 IPAddress ? address ;
246286
@@ -261,7 +301,7 @@ public MultiProxy GetMultiProxy(Uri uri)
261301 {
262302 // Host is valid IP address.
263303 // Check if it belongs to local system.
264- foreach ( IPAddress a in _localIp ! )
304+ foreach ( IPAddress a in _localIp )
265305 {
266306 if ( a . Equals ( address ) )
267307 {
0 commit comments