PageRenderTime 25ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/mcs/class/referencesource/System/net/System/Net/WinHttpWebProxyFinder.cs

https://github.com/pruiz/mono
C# | 345 lines | 232 code | 49 blank | 64 comment | 43 complexity | 55fe4d52c8eb189159e6ab759236f622 MD5 | raw file
Possible License(s): LGPL-2.0, MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0
  1. using System.Text;
  2. using System.Collections.Generic;
  3. using System.Runtime.InteropServices;
  4. using Microsoft.Win32;
  5. using System.Runtime.CompilerServices;
  6. using System.Net.Configuration;
  7. namespace System.Net
  8. {
  9. // This class uses WinHttp APIs only to find, download and execute the PAC file.
  10. internal sealed class WinHttpWebProxyFinder : BaseWebProxyFinder
  11. {
  12. private SafeInternetHandle session;
  13. private bool autoDetectFailed;
  14. public WinHttpWebProxyFinder(AutoWebProxyScriptEngine engine)
  15. : base(engine)
  16. {
  17. // Don't specify a user agent and dont' specify proxy settings. This is the same behavior WinHttp
  18. // uses when downloading the PAC file.
  19. session = UnsafeNclNativeMethods.WinHttp.WinHttpOpen(null,
  20. UnsafeNclNativeMethods.WinHttp.AccessType.NoProxy, null, null, 0);
  21. // Don't throw on error, just log the error information. This is consistent with how auto-proxy
  22. // works: we never throw on error (discovery, download, execution errors).
  23. if (session == null || session.IsInvalid)
  24. {
  25. int errorCode = GetLastWin32Error();
  26. if (Logging.On) Logging.PrintError(Logging.Web, SR.GetString(SR.net_log_proxy_winhttp_cant_open_session, errorCode));
  27. }
  28. else
  29. {
  30. // The default download-timeout is 1 min.
  31. // WinHTTP will use the sum of all four timeouts provided in WinHttpSetTimeouts as the
  32. // actual timeout. Setting a value to 0 means "infinite".
  33. // Since we don't provide the ability to specify finegrained timeouts like WinHttp does,
  34. // we simply apply the configured timeout to all four WinHttp timeouts.
  35. int timeout = SettingsSectionInternal.Section.DownloadTimeout;
  36. if (!UnsafeNclNativeMethods.WinHttp.WinHttpSetTimeouts(session, timeout, timeout, timeout, timeout))
  37. {
  38. // We weren't able to set the timeouts. Just log and continue.
  39. int errorCode = GetLastWin32Error();
  40. if (Logging.On) Logging.PrintError(Logging.Web, SR.GetString(SR.net_log_proxy_winhttp_timeout_error, errorCode));
  41. }
  42. }
  43. }
  44. public override bool GetProxies(Uri destination, out IList<string> proxyList)
  45. {
  46. proxyList = null;
  47. if (session == null || session.IsInvalid)
  48. {
  49. return false;
  50. }
  51. if (State == AutoWebProxyState.UnrecognizedScheme)
  52. {
  53. // If a previous call already determined that we don't support the scheme of the script
  54. // location, then just return false.
  55. return false;
  56. }
  57. string proxyListString = null;
  58. // Set to auto-detect failed. In case auto-detect is turned off and a script-location is available
  59. // we'll try downloading the script from that location.
  60. int errorCode = (int)UnsafeNclNativeMethods.WinHttp.ErrorCodes.AudodetectionFailed;
  61. // If auto-detect is turned on, try to execute DHCP/DNS query to get PAC file, then run the script
  62. if (Engine.AutomaticallyDetectSettings && !autoDetectFailed)
  63. {
  64. errorCode = GetProxies(destination, null, out proxyListString);
  65. // Remember if auto-detect failed. If config-script works, then the next time GetProxies() is
  66. // called, we'll not try auto-detect but jump right to config-script.
  67. autoDetectFailed = IsErrorFatalForAutoDetect(errorCode);
  68. if (errorCode == (int)UnsafeNclNativeMethods.WinHttp.ErrorCodes.UnrecognizedScheme)
  69. {
  70. // DHCP returned FILE or FTP scheme for the PAC file location: We should stop here
  71. // since this is not an error, but a feature WinHttp doesn't currently support. The
  72. // caller may be able to handle this case by using another WebProxyFinder.
  73. State = AutoWebProxyState.UnrecognizedScheme;
  74. return false;
  75. }
  76. }
  77. // If auto-detect failed or was turned off, and a config-script location is available, download
  78. // the script from that location and execute it.
  79. if ((Engine.AutomaticConfigurationScript != null) && (IsRecoverableAutoProxyError(errorCode)))
  80. {
  81. errorCode = GetProxies(destination, Engine.AutomaticConfigurationScript,
  82. out proxyListString);
  83. }
  84. State = GetStateFromErrorCode(errorCode);
  85. if (State == AutoWebProxyState.Completed)
  86. {
  87. if (string.IsNullOrEmpty(proxyListString))
  88. {
  89. // In this case the PAC file execution returned "DIRECT", i.e. WinHttp returned
  90. // 'true' with a 'null' proxy string. This state is represented as a list
  91. // containing one element with value 'null'.
  92. proxyList = new string[1] { null };
  93. }
  94. else
  95. {
  96. // WinHttp doesn't really clear all whitespaces. It does a pretty good job with
  97. // spaces, but e.g. tabs aren't removed. Therefore make sure all whitespaces get
  98. // removed.
  99. // Note: Even though the PAC script could use space characters as separators,
  100. // WinHttp will always use ';' as separator character. E.g. for the PAC result
  101. // "PROXY 192.168.0.1 PROXY 192.168.0.2" WinHttp will return "192.168.0.1;192.168.0.2".
  102. // WinHttp will also remove trailing ';'.
  103. proxyListString = RemoveWhitespaces(proxyListString);
  104. proxyList = proxyListString.Split(';');
  105. }
  106. return true;
  107. }
  108. // We get here if something went wrong, or if neither auto-detect nor script-location
  109. // were turned on.
  110. return false;
  111. }
  112. public override void Abort()
  113. {
  114. // WinHttp doesn't support aborts. Therefore we can't do anything here.
  115. }
  116. public override void Reset()
  117. {
  118. base.Reset();
  119. // Reset auto-detect failure: If the connection changes, we may be able to do auto-detect again.
  120. autoDetectFailed = false;
  121. }
  122. protected override void Dispose(bool disposing)
  123. {
  124. if (disposing)
  125. {
  126. if (session != null && !session.IsInvalid)
  127. {
  128. session.Close();
  129. }
  130. }
  131. }
  132. private int GetProxies(Uri destination, Uri scriptLocation, out string proxyListString)
  133. {
  134. int errorCode = 0;
  135. proxyListString = null;
  136. UnsafeNclNativeMethods.WinHttp.WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions =
  137. new UnsafeNclNativeMethods.WinHttp.WINHTTP_AUTOPROXY_OPTIONS();
  138. // Always try to download the PAC file without authentication. If we turn auth. on, the WinHttp
  139. // service will create a new session for every request (performance/memory implications).
  140. // Therefore we only turn auto-logon on if it is really needed.
  141. autoProxyOptions.AutoLogonIfChallenged = false;
  142. if (scriptLocation == null)
  143. {
  144. // Use auto-discovery to find the script location.
  145. autoProxyOptions.Flags = UnsafeNclNativeMethods.WinHttp.AutoProxyFlags.AutoDetect;
  146. autoProxyOptions.AutoConfigUrl = null;
  147. autoProxyOptions.AutoDetectFlags = UnsafeNclNativeMethods.WinHttp.AutoDetectType.Dhcp |
  148. UnsafeNclNativeMethods.WinHttp.AutoDetectType.DnsA;
  149. }
  150. else
  151. {
  152. // Use the provided script location for the PAC file.
  153. autoProxyOptions.Flags = UnsafeNclNativeMethods.WinHttp.AutoProxyFlags.AutoProxyConfigUrl;
  154. autoProxyOptions.AutoConfigUrl = scriptLocation.ToString();
  155. autoProxyOptions.AutoDetectFlags = UnsafeNclNativeMethods.WinHttp.AutoDetectType.None;
  156. }
  157. if (!WinHttpGetProxyForUrl(destination.ToString(), ref autoProxyOptions, out proxyListString))
  158. {
  159. errorCode = GetLastWin32Error();
  160. // If the PAC file can't be downloaded because auth. was required, we check if the
  161. // credentials are set; if so, then we try again using auto-logon.
  162. // Note that by default webProxy.Credentials will be null. The user needs to set
  163. // <defaultProxy useDefaultCredentials="true"> in the config file, in order for
  164. // webProxy.Credentials to be set to DefaultNetworkCredentials.
  165. if ((errorCode == (int)UnsafeNclNativeMethods.WinHttp.ErrorCodes.LoginFailure) &&
  166. (Engine.Credentials != null))
  167. {
  168. // Now we need to try again, this time by enabling auto-logon.
  169. autoProxyOptions.AutoLogonIfChallenged = true;
  170. if (!WinHttpGetProxyForUrl(destination.ToString(), ref autoProxyOptions,
  171. out proxyListString))
  172. {
  173. errorCode = GetLastWin32Error();
  174. }
  175. }
  176. if (Logging.On) Logging.PrintError(Logging.Web, SR.GetString(SR.net_log_proxy_winhttp_getproxy_failed, destination, errorCode));
  177. }
  178. return errorCode;
  179. }
  180. private bool WinHttpGetProxyForUrl(string destination,
  181. ref UnsafeNclNativeMethods.WinHttp.WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions,
  182. out string proxyListString)
  183. {
  184. proxyListString = null;
  185. bool success = false;
  186. UnsafeNclNativeMethods.WinHttp.WINHTTP_PROXY_INFO proxyInfo =
  187. new UnsafeNclNativeMethods.WinHttp.WINHTTP_PROXY_INFO();
  188. // Make sure the strings get cleaned up in a CER (thus unexpected exceptions, like
  189. // ThreadAbortException will not interrupt the execution of the finally block, and we'll not
  190. // leak resources).
  191. RuntimeHelpers.PrepareConstrainedRegions();
  192. try
  193. {
  194. success = UnsafeNclNativeMethods.WinHttp.WinHttpGetProxyForUrl(session,
  195. destination, ref autoProxyOptions, out proxyInfo);
  196. if (success)
  197. {
  198. proxyListString = Marshal.PtrToStringUni(proxyInfo.Proxy);
  199. }
  200. }
  201. finally
  202. {
  203. Marshal.FreeHGlobal(proxyInfo.Proxy);
  204. Marshal.FreeHGlobal(proxyInfo.ProxyBypass);
  205. }
  206. return success;
  207. }
  208. private static int GetLastWin32Error()
  209. {
  210. int errorCode = Marshal.GetLastWin32Error();
  211. if (errorCode == NativeMethods.ERROR_NOT_ENOUGH_MEMORY)
  212. {
  213. throw new OutOfMemoryException();
  214. }
  215. return errorCode;
  216. }
  217. private static bool IsRecoverableAutoProxyError(int errorCode)
  218. {
  219. GlobalLog.Assert(errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_INVALID_PARAMETER,
  220. "WinHttpGetProxyForUrl() call: Error code 'Invalid parameter' should not be returned.");
  221. // According to WinHttp the following states can be considered "recoverable", i.e.
  222. // we should continue trying WinHttpGetProxyForUrl() with the provided script-location
  223. // (if available).
  224. switch ((UnsafeNclNativeMethods.WinHttp.ErrorCodes)errorCode)
  225. {
  226. case UnsafeNclNativeMethods.WinHttp.ErrorCodes.AutoProxyServiceError:
  227. case UnsafeNclNativeMethods.WinHttp.ErrorCodes.AudodetectionFailed:
  228. case UnsafeNclNativeMethods.WinHttp.ErrorCodes.BadAutoProxyScript:
  229. case UnsafeNclNativeMethods.WinHttp.ErrorCodes.LoginFailure:
  230. case UnsafeNclNativeMethods.WinHttp.ErrorCodes.OperationCancelled:
  231. case UnsafeNclNativeMethods.WinHttp.ErrorCodes.Timeout:
  232. case UnsafeNclNativeMethods.WinHttp.ErrorCodes.UnableToDownloadScript:
  233. case UnsafeNclNativeMethods.WinHttp.ErrorCodes.UnrecognizedScheme:
  234. return true;
  235. }
  236. return false;
  237. }
  238. private static AutoWebProxyState GetStateFromErrorCode(int errorCode)
  239. {
  240. if (errorCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
  241. {
  242. return AutoWebProxyState.Completed;
  243. }
  244. switch ((UnsafeNclNativeMethods.WinHttp.ErrorCodes)errorCode)
  245. {
  246. case UnsafeNclNativeMethods.WinHttp.ErrorCodes.AudodetectionFailed:
  247. return AutoWebProxyState.DiscoveryFailure;
  248. case UnsafeNclNativeMethods.WinHttp.ErrorCodes.UnableToDownloadScript:
  249. return AutoWebProxyState.DownloadFailure;
  250. case UnsafeNclNativeMethods.WinHttp.ErrorCodes.UnrecognizedScheme:
  251. return AutoWebProxyState.UnrecognizedScheme;
  252. case UnsafeNclNativeMethods.WinHttp.ErrorCodes.BadAutoProxyScript:
  253. case UnsafeNclNativeMethods.WinHttp.ErrorCodes.InvalidUrl:
  254. case UnsafeNclNativeMethods.WinHttp.ErrorCodes.AutoProxyServiceError:
  255. // AutoProxy succeeded, but no proxy could be found for this request
  256. return AutoWebProxyState.Completed;
  257. default:
  258. // We don't know the exact cause of the failure. Set the state to compilation failure to
  259. // indicate that something went wrong.
  260. return AutoWebProxyState.CompilationFailure;
  261. }
  262. }
  263. private static string RemoveWhitespaces(string value)
  264. {
  265. StringBuilder result = new StringBuilder();
  266. foreach (char c in value)
  267. {
  268. if (!char.IsWhiteSpace(c))
  269. {
  270. result.Append(c);
  271. }
  272. }
  273. return result.ToString();
  274. }
  275. // Should we ignore auto-detect from now on?
  276. // http://msdn.microsoft.com/en-us/library/aa384097(VS.85).aspx
  277. private static bool IsErrorFatalForAutoDetect(int errorCode)
  278. {
  279. switch ((UnsafeNclNativeMethods.WinHttp.ErrorCodes)errorCode)
  280. {
  281. case UnsafeNclNativeMethods.WinHttp.ErrorCodes.Success:
  282. case UnsafeNclNativeMethods.WinHttp.ErrorCodes.InvalidUrl:
  283. // Some URIs are not supported (like Unicode hosts on Win7 and lower),
  284. // but our proxy is still valid
  285. case UnsafeNclNativeMethods.WinHttp.ErrorCodes.BadAutoProxyScript:
  286. // Got the script, but something went wrong in execution. For example,
  287. // the request was for an unresolvable single label name.
  288. case UnsafeNclNativeMethods.WinHttp.ErrorCodes.AutoProxyServiceError:
  289. // Returned when a proxy for the specified URL cannot be located.
  290. return false;
  291. default:
  292. return true;
  293. }
  294. }
  295. }
  296. }