1 //------------------------------------------------------------------------------
2 // <copyright file="webproxy.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
8 using System
.Net
.NetworkInformation
;
9 using System
.Globalization
;
10 using System
.Security
.Permissions
;
12 using System
.Text
.RegularExpressions
;
13 using System
.Collections
;
14 using System
.Runtime
.Serialization
;
15 using System
.Collections
.Generic
;
16 using System
.Diagnostics
.CodeAnalysis
;
19 internal bool bypassOnLocal
;
20 internal bool automaticallyDetectSettings
;
21 internal Uri proxyAddress
;
22 internal Hashtable proxyHostAddresses
;
23 internal Uri scriptLocation
;
24 #if USE_WINIET_AUTODETECT_CACHE
25 internal Uri lkgScriptLocation
;
27 internal ArrayList bypassList
;
30 // Handles default proxy setting implementation for the Http proxy.
32 // The following order is used when determinig proxy settings:
33 // 1. web.config/app.config: if available, use settings specified in <system.net><defaultProxy>
34 // 2. If the config file doesn't contain proxy settings, read the IE proxy settings
36 // If the IE proxy settings contain invalid settings (e.g. "invalid;host" - note the semicolon), then
37 // a FormatException will be thrown.
39 public class WebProxy
: IAutoWebProxy
, ISerializable
{
40 // these are settable by the user
41 private bool _UseRegistry
; // This is just around for serialization. Can we get rid of it?
42 private bool _BypassOnLocal
;
43 private bool m_EnableAutoproxy
;
44 private Uri _ProxyAddress
;
45 private ArrayList _BypassList
;
46 private ICredentials _Credentials
;
48 // these are computed on the fly
49 private Regex
[] _RegExBypassList
;
50 private Hashtable _ProxyHostAddresses
;
53 /// <para>[To be supplied.]</para>
56 : this((Uri
) null, false, null, null) {
60 /// <para>[To be supplied.]</para>
62 public WebProxy(Uri Address
)
63 : this(Address
, false, null, null) {
67 /// <para>[To be supplied.]</para>
69 public WebProxy(Uri Address
, bool BypassOnLocal
)
70 : this(Address
, BypassOnLocal
, null, null) {
74 /// <para>[To be supplied.]</para>
76 public WebProxy(Uri Address
, bool BypassOnLocal
, string[] BypassList
)
77 : this(Address
, BypassOnLocal
, BypassList
, null) {
81 /// <para>[To be supplied.]</para>
83 public WebProxy(Uri Address
, bool BypassOnLocal
, string[] BypassList
, ICredentials Credentials
) {
84 _ProxyAddress
= Address
;
85 _BypassOnLocal
= BypassOnLocal
;
86 if (BypassList
!= null) {
87 _BypassList
= new ArrayList(BypassList
);
88 UpdateRegExList(true);
90 _Credentials
= Credentials
;
91 m_EnableAutoproxy
= true;
95 /// <para>[To be supplied.]</para>
97 public WebProxy(string Host
, int Port
)
98 : this(new Uri("http://" + Host
+ ":" + Port
.ToString(CultureInfo
.InvariantCulture
)), false, null, null) {
102 /// <para>[To be supplied.]</para>
104 public WebProxy(string Address
)
105 : this(CreateProxyUri(Address
), false, null, null) {
109 /// <para>[To be supplied.]</para>
111 public WebProxy(string Address
, bool BypassOnLocal
)
112 : this(CreateProxyUri(Address
), BypassOnLocal
, null, null) {
116 /// <para>[To be supplied.]</para>
118 public WebProxy(string Address
, bool BypassOnLocal
, string[] BypassList
)
119 : this(CreateProxyUri(Address
), BypassOnLocal
, BypassList
, null) {
123 /// <para>[To be supplied.]</para>
125 public WebProxy(string Address
, bool BypassOnLocal
, string[] BypassList
, ICredentials Credentials
)
126 : this(CreateProxyUri(Address
), BypassOnLocal
, BypassList
, Credentials
) {
130 /// <para>[To be supplied.]</para>
136 #endif // !FEATURE_PAL
137 return _ProxyAddress
;
140 _UseRegistry
= false;
141 DeleteScriptEngine();
142 _ProxyHostAddresses
= null; // hash list of proxies
143 _ProxyAddress
= value;
148 /// <para>[To be supplied.]</para>
150 internal bool AutoDetect
153 GlobalLog
.Assert(_UseRegistry
== false, "Cannot set AutoDetect if we are using registry for proxy settings");
154 GlobalLog
.Assert(m_EnableAutoproxy
, "WebProxy#{0}::.ctor()|Cannot set AutoDetect if usesystemdefault is set.", ValidationHelper
.HashString(this));
156 if (ScriptEngine
== null)
158 ScriptEngine
= new AutoWebProxyScriptEngine(this, false);
160 ScriptEngine
.AutomaticallyDetectSettings
= value;
165 /// <para>[To be supplied.]</para>
167 internal Uri ScriptLocation
{
169 GlobalLog
.Assert(value != null, "Cannot set ScriptLocation to null");
170 GlobalLog
.Assert(_UseRegistry
== false, "Cannot set AutoDetect if we are using registry for proxy settings");
171 GlobalLog
.Assert(m_EnableAutoproxy
, "WebProxy#{0}::.ctor()|Cannot set ScriptLocation if usesystemdefault is set.", ValidationHelper
.HashString(this));
173 if (ScriptEngine
== null)
175 ScriptEngine
= new AutoWebProxyScriptEngine(this, false);
177 ScriptEngine
.AutomaticConfigurationScript
= value;
182 /// <para>[To be supplied.]</para>
184 public bool BypassProxyOnLocal
{
188 #endif // !FEATURE_PAL
189 return _BypassOnLocal
;
192 _UseRegistry
= false;
193 DeleteScriptEngine();
194 _BypassOnLocal
= value;
199 /// <para>[To be supplied.]</para>
201 public string[] BypassList
{
205 #endif // !FEATURE_PAL
206 if (_BypassList
== null) {
207 _BypassList
= new ArrayList();
209 return (string[])_BypassList
.ToArray(typeof(string));
212 _UseRegistry
= false;
213 DeleteScriptEngine();
214 _BypassList
= new ArrayList(value);
215 UpdateRegExList(true);
220 /// <para>[To be supplied.]</para>
222 public ICredentials Credentials
{
227 _Credentials
= value;
232 /// <para>Sets Credentials to CredentialCache.DefaultCredentials</para>
234 public bool UseDefaultCredentials
{
236 return (Credentials
is SystemNetworkCredential
) ? true : false;
239 _Credentials
= value ? CredentialCache
.DefaultCredentials
: null;
244 /// <para>[To be supplied.]</para>
246 public ArrayList BypassArrayList
{
250 #endif // !FEATURE_PAL
251 if ( _BypassList
== null ) {
252 _BypassList
= new ArrayList();
258 internal void CheckForChanges() {
259 if (ScriptEngine
!= null)
261 ScriptEngine
.CheckForChanges();
266 /// <para>[To be supplied.]</para>
268 public Uri
GetProxy(Uri destination
) {
269 GlobalLog
.Print("WebProxy#" + ValidationHelper
.HashString(this) + "::GetProxy() destination:" + ValidationHelper
.ToString(destination
));
270 if (destination
== null)
272 throw new ArgumentNullException("destination");
276 if (GetProxyAuto(destination
, out result
)) {
279 if (IsBypassedManual(destination
)) {
282 Hashtable proxyHostAddresses
= _ProxyHostAddresses
;
283 Uri proxy
= proxyHostAddresses
!=null ? proxyHostAddresses
[destination
.Scheme
] as Uri
: _ProxyAddress
;
284 return proxy
!=null? proxy
: destination
;
288 // CreateProxyUri - maps string to Uri
291 private static Uri
CreateProxyUri(string address
) {
292 if (address
== null) {
295 if (address
.IndexOf("://") == -1) {
296 address
= "http://" + address
;
298 return new Uri(address
);
302 // UpdateRegExList - Update internal _RegExBypassList
303 // warning - can throw if the RegEx doesn't parse??
305 private void UpdateRegExList(bool canThrow
) {
306 Regex
[] regExBypassList
= null;
307 ArrayList bypassList
= _BypassList
;
309 if ( bypassList
!= null && bypassList
.Count
> 0 ) {
310 regExBypassList
= new Regex
[bypassList
.Count
];
311 for (int i
= 0; i
< bypassList
.Count
; i
++ ) {
312 regExBypassList
[i
] = new Regex((string)bypassList
[i
], RegexOptions
.IgnoreCase
| RegexOptions
.CultureInvariant
);
318 _RegExBypassList
= null;
323 // only update here, cause it could throw earlier in the loop
324 _RegExBypassList
= regExBypassList
;
328 // IsMatchInBypassList - match input against _RegExBypassList
330 private bool IsMatchInBypassList(Uri input
) {
331 UpdateRegExList(false);
332 if ( _RegExBypassList
== null ) {
335 string matchUriString
= input
.Scheme
+ "://" + input
.Host
+ (!input
.IsDefaultPort
? (":"+input
.Port
) : "" );
336 for (int i
= 0; i
< _BypassList
.Count
; i
++ ) {
337 if (_RegExBypassList
[i
].IsMatch(matchUriString
)) {
345 /// Determines if the host Uri should be routed locally or go through the proxy.
347 private bool IsLocal(Uri host
) {
348 string hostString
= host
.Host
;
350 IPAddress hostAddress
;
351 if (IPAddress
.TryParse(hostString
, out hostAddress
))
353 return (IPAddress
.IsLoopback(hostAddress
) || NclUtilities
.IsAddressLocal(hostAddress
));
356 int dot
= hostString
.IndexOf('.');
364 // If it matches the primary domain, it's local. (Whether or not the hostname matches.)
365 string local
= "." + IPGlobalProperties
.InternalGetIPGlobalProperties().DomainName
;
366 if (local
!= null && local
.Length
== (hostString
.Length
- dot
) &&
367 string.Compare(local
, 0, hostString
, dot
, local
.Length
, StringComparison
.OrdinalIgnoreCase
) == 0) {
374 /// Determines if the host Uri should be routed locally or go through a proxy.
376 private bool IsLocalInProxyHash(Uri host
) {
377 Hashtable proxyHostAddresses
= _ProxyHostAddresses
;
378 if (proxyHostAddresses
!= null) {
379 Uri proxy
= (Uri
) proxyHostAddresses
[host
.Scheme
];
381 return true; // no proxy entry for this scheme, then bypass
389 /// <para>[To be supplied.]</para>
391 public bool IsBypassed(Uri host
) {
392 GlobalLog
.Print("WebProxy#" + ValidationHelper
.HashString(this) + "::IsBypassed() destination:" + ValidationHelper
.ToString(host
));
395 throw new ArgumentNullException("host");
399 if (IsBypassedAuto(host
, out result
)) {
402 return IsBypassedManual(host
);
405 private bool IsBypassedManual(Uri host
) {
406 if (host
.IsLoopback
) {
407 return true; // bypass localhost from using a proxy.
409 return (_ProxyAddress
==null && _ProxyHostAddresses
==null) || (_BypassOnLocal
&& IsLocal(host
)) || IsMatchInBypassList(host
) || IsLocalInProxyHash(host
);
413 /// <para>[To be supplied.]</para>
415 [Obsolete("This method has been deprecated. Please use the proxy selected for you by default. http://go.microsoft.com/fwlink/?linkid=14202")]
416 public static WebProxy
GetDefaultProxy() {
418 ExceptionHelper
.WebPermissionUnrestricted
.Demand();
420 return new WebProxy(true);
424 // ISerializable constructor
427 /// <para>[To be supplied.]</para>
429 protected WebProxy(SerializationInfo serializationInfo
, StreamingContext streamingContext
) {
430 // first check for useRegistry on the serialized proxy
431 bool useRegistry
= false;
433 useRegistry
= serializationInfo
.GetBoolean("_UseRegistry");
438 // just make the proxy advanced, don't populate with any settings
439 // note - this will happen in the context of the user performing the deserialization (their proxy settings get read)
441 ExceptionHelper
.WebPermissionUnrestricted
.Demand();
443 UnsafeUpdateFromRegistry();
447 _ProxyAddress
= (Uri
)serializationInfo
.GetValue("_ProxyAddress", typeof(Uri
));
448 _BypassOnLocal
= serializationInfo
.GetBoolean("_BypassOnLocal");
449 _BypassList
= (ArrayList
)serializationInfo
.GetValue("_BypassList", typeof(ArrayList
));
451 UseDefaultCredentials
= serializationInfo
.GetBoolean("_UseDefaultCredentials");
458 // ISerializable method
462 /// <para>[To be supplied.]</para>
464 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase", Justification
= "System.dll is still using pre-v4 security model and needs this demand")]
465 [SecurityPermission(SecurityAction
.LinkDemand
, Flags
=SecurityPermissionFlag
.SerializationFormatter
, SerializationFormatter
=true)]
466 void ISerializable
.GetObjectData(SerializationInfo serializationInfo
, StreamingContext streamingContext
)
468 GetObjectData(serializationInfo
, streamingContext
);
472 // FxCop: provide a way for derived classes to access this method even if they reimplement ISerializable.
474 [SecurityPermission(SecurityAction
.LinkDemand
, SerializationFormatter
=true)]
475 protected virtual void GetObjectData(SerializationInfo serializationInfo
, StreamingContext streamingContext
)
477 serializationInfo
.AddValue("_BypassOnLocal", _BypassOnLocal
);
478 serializationInfo
.AddValue("_ProxyAddress", _ProxyAddress
);
479 serializationInfo
.AddValue("_BypassList", _BypassList
);
480 serializationInfo
.AddValue("_UseDefaultCredentials", UseDefaultCredentials
);
482 serializationInfo
.AddValue("_UseRegistry", true);
488 /// Handles proxy settings by using Internet Explorer based settings,
489 /// keep in mind the security implications when downloading and running
490 /// script from any network source configured in Internet Explorer.
493 private AutoWebProxyScriptEngine m_ScriptEngine
;
495 internal AutoWebProxyScriptEngine ScriptEngine
{
497 return m_ScriptEngine
;
500 m_ScriptEngine
= value;
505 public static IWebProxy
CreateDefaultProxy ()
508 throw new PlatformNotSupportedException ();
510 return Mono
.Net
.CFNetwork
.GetDefaultProxy ();
512 // Return the system web proxy. This only works for ICS+.
513 var data
= AndroidPlatform
.GetDefaultProxy ();
517 if (Platform
.IsMacOS
) {
518 var data
= Mono
.Net
.CFNetwork
.GetDefaultProxy ();
524 return new WebProxy (true);
528 // This constructor is used internally to make WebProxies that read their state from the registry.
530 internal WebProxy(bool enableAutoproxy
)
532 m_EnableAutoproxy
= enableAutoproxy
;
533 UnsafeUpdateFromRegistry();
536 internal void DeleteScriptEngine() {
537 if (ScriptEngine
!= null) {
538 ScriptEngine
.Close();
543 internal void UnsafeUpdateFromRegistry() {
544 GlobalLog
.Assert(!_UseRegistry
, "WebProxy#{0}::UnsafeUpdateFromRegistry()|_UseRegistry ScriptEngine#{1}", ValidationHelper
.HashString(this), ValidationHelper
.HashString(m_ScriptEngine
));
546 #if !FEATURE_PAL || !MOBILE
547 ScriptEngine
= new AutoWebProxyScriptEngine(this, true);
548 WebProxyData webProxyData
= ScriptEngine
.GetWebProxyData();
550 Update(webProxyData
);
554 internal void Update(WebProxyData webProxyData
) {
556 GlobalLog
.Print("WebProxy#" + ValidationHelper
.HashString(this) + "::Update() Before " + DumpIWebProxy(this));
558 // update needs to happen atomically
560 _BypassOnLocal
= webProxyData
.bypassOnLocal
;
561 _ProxyAddress
= webProxyData
.proxyAddress
;
562 _ProxyHostAddresses
= webProxyData
.proxyHostAddresses
;
563 _BypassList
= webProxyData
.bypassList
;
565 ScriptEngine
.AutomaticallyDetectSettings
= m_EnableAutoproxy
&& webProxyData
.automaticallyDetectSettings
;
566 ScriptEngine
.AutomaticConfigurationScript
= m_EnableAutoproxy
? webProxyData
.scriptLocation
: null;
569 GlobalLog
.Print("WebProxy#" + ValidationHelper
.HashString(this) + "::Update() After " + DumpIWebProxy(this));
575 /// We really didn't want to expose this. IWebProxy is kind of broken so we needed
576 /// a different way of calling into IsBypassed/GetProxy with a single method call.
577 /// We need to make it public though, so it is. This API will return null if
578 /// the proxy is to be bypassed, otherwise it returns an array of Uri to proxise
579 /// that may be used to access the destination. If an entry in the array is null
580 /// we want to try a direct access. Today we only attempt using the first entry.
583 ProxyChain IAutoWebProxy
.GetProxies(Uri destination
) {
584 GlobalLog
.Print("WebProxy#" + ValidationHelper
.HashString(this) + "::GetProxies() destination:" + ValidationHelper
.ToString(destination
));
585 if (destination
== null)
587 throw new ArgumentNullException("destination");
589 return new ProxyScriptChain(this, destination
);
593 internal static string DumpIWebProxy(IWebProxy proxy
) {
594 StringBuilder stringBuilder
= new StringBuilder();
595 stringBuilder
.Append(" Type: " + ValidationHelper
.ToString(proxy
.GetType()) + "\r\n");
596 WebProxy webProxy
= proxy
as WebProxy
;
597 if (webProxy
!=null) {
598 stringBuilder
.Append(" - Address: " + ValidationHelper
.ToString(webProxy
._ProxyAddress
) + "\r\n");
599 stringBuilder
.Append(" - BypassProxyOnLocal: " + ValidationHelper
.ToString(webProxy
._BypassOnLocal
) + "\r\n");
601 stringBuilder
.Append(" - -------------------------------------------------");
602 return stringBuilder
.ToString();
607 // IWebProxy implementation
610 // Get proxies can never return null in the case of ExecutionSuccess.
611 private bool GetProxyAuto(Uri destination
, out Uri proxyUri
) {
612 GlobalLog
.Print("WebProxy#" + ValidationHelper
.HashString(this) + "::GetProxyAuto() destination:" + ValidationHelper
.ToString(destination
));
615 if (ScriptEngine
== null) {
618 IList
<string> proxies
= null;
619 if (!ScriptEngine
.GetProxies(destination
, out proxies
)) {
623 // Returning null in case 'proxies.Count == 0' means, no proxy available (incl. DIRECT), the request is prohibited.
624 if (proxies
.Count
> 0) {
625 if (AreAllBypassed(proxies
, true)) {
626 // this is the broken behaviour of IWebProxy. Returning the same destination means bypass
627 proxyUri
= destination
;
630 proxyUri
= ProxyUri(proxies
[0]);
636 private bool IsBypassedAuto(Uri destination
, out bool isBypassed
) {
637 GlobalLog
.Print("WebProxy#" + ValidationHelper
.HashString(this) + "::IsBypassedAuto() destination:" + ValidationHelper
.ToString(destination
));
641 if (ScriptEngine
== null) {
644 IList
<string> proxyList
;
645 if (!ScriptEngine
.GetProxies(destination
, out proxyList
)) {
648 if (proxyList
.Count
== 0) {
652 isBypassed
= AreAllBypassed(proxyList
, true);
657 internal Uri
[] GetProxiesAuto(Uri destination
, ref int syncStatus
)
659 GlobalLog
.Print("WebProxy#" + ValidationHelper
.HashString(this) + "::GetProxiesAuto() destination:" + ValidationHelper
.ToString(destination
));
661 if (ScriptEngine
== null) {
665 IList
<string> proxyList
= null;
666 if (!ScriptEngine
.GetProxies(destination
, out proxyList
, ref syncStatus
)) {
670 Uri
[] proxyUris
= null;
671 if (proxyList
.Count
== 0) {
672 proxyUris
= new Uri
[] { }
;
674 else if (AreAllBypassed(proxyList
, false)) {
675 proxyUris
= new Uri
[] { null }
;
678 proxyUris
= new Uri
[proxyList
.Count
];
679 for (int i
= 0; i
< proxyList
.Count
; i
++) {
680 proxyUris
[i
] = ProxyUri(proxyList
[i
]);
686 internal void AbortGetProxiesAuto(ref int syncStatus
)
688 if (ScriptEngine
!= null)
690 ScriptEngine
.Abort(ref syncStatus
);
694 internal Uri
GetProxyAutoFailover(Uri destination
)
696 if (IsBypassedManual(destination
))
701 Uri proxy
= _ProxyAddress
;
702 Hashtable proxyHostAddresses
= _ProxyHostAddresses
;
703 if (proxyHostAddresses
!= null)
705 proxy
= proxyHostAddresses
[destination
.Scheme
] as Uri
;
710 private static bool AreAllBypassed(IEnumerable
<string> proxies
, bool checkFirstOnly
) {
711 bool isBypassed
= true;
713 foreach (string proxy
in proxies
) {
714 isBypassed
= string.IsNullOrEmpty(proxy
);
716 if (checkFirstOnly
|| !isBypassed
) {
724 private static Uri
ProxyUri(string proxyName
) {
725 return proxyName
==null || proxyName
.Length
==0 ? null : new Uri("http://" + proxyName
);