public static void DisposeAndClearHandle(ref SafeWinHttpHandle?safeHandle) { if (safeHandle != null) { safeHandle.Dispose(); safeHandle = null; } }
protected override bool ReleaseHandle() { if (_parentHandle != null) { _parentHandle.DangerousRelease(); _parentHandle = null; } return(Interop.WinHttp.WinHttpCloseHandle(handle)); }
public void SetParentHandle(SafeWinHttpHandle parentHandle) { Debug.Assert(_parentHandle == null); Debug.Assert(parentHandle != null); Debug.Assert(!parentHandle.IsInvalid); bool ignore = false; parentHandle.DangerousAddRef(ref ignore); _parentHandle = parentHandle; }
public static bool TryCreate([NotNullWhen(true)] out IWebProxy?proxy) { // This will get basic proxy setting from system using existing // WinInetProxyHelper functions. If no proxy is enabled, it will return null. SafeWinHttpHandle?sessionHandle = null; proxy = null; WinInetProxyHelper proxyHelper = new WinInetProxyHelper(); if (!proxyHelper.ManualSettingsOnly && !proxyHelper.AutoSettingsUsed) { return(false); } if (proxyHelper.AutoSettingsUsed) { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(proxyHelper, $"AutoSettingsUsed, calling {nameof(Interop.WinHttp.WinHttpOpen)}"); } sessionHandle = Interop.WinHttp.WinHttpOpen( IntPtr.Zero, Interop.WinHttp.WINHTTP_ACCESS_TYPE_NO_PROXY, Interop.WinHttp.WINHTTP_NO_PROXY_NAME, Interop.WinHttp.WINHTTP_NO_PROXY_BYPASS, (int)Interop.WinHttp.WINHTTP_FLAG_ASYNC); if (sessionHandle.IsInvalid) { // Proxy failures are currently ignored by managed handler. if (NetEventSource.Log.IsEnabled()) { NetEventSource.Error(proxyHelper, $"{nameof(Interop.WinHttp.WinHttpOpen)} returned invalid handle"); } sessionHandle.Dispose(); return(false); } } proxy = new HttpWindowsProxy(proxyHelper, sessionHandle); return(true); }
private static void ResetAuthRequestHeaders(WinHttpRequestState state) { const string AuthHeaderNameWithColon = "Authorization:"; SafeWinHttpHandle?requestHandle = state.RequestHandle; Debug.Assert(requestHandle != null); // Clear auth headers. if (!Interop.WinHttp.WinHttpAddRequestHeaders( requestHandle, AuthHeaderNameWithColon, (uint)AuthHeaderNameWithColon.Length, Interop.WinHttp.WINHTTP_ADDREQ_FLAG_REPLACE)) { int lastError = Marshal.GetLastWin32Error(); if (lastError != Interop.WinHttp.ERROR_WINHTTP_HEADER_NOT_FOUND) { throw WinHttpException.CreateExceptionUsingError(lastError, "WINHTTP_CALLBACK_STATUS_REDIRECT/WinHttpAddRequestHeaders"); } } }
public static void AddResponseCookiesToContainer(WinHttpRequestState state) { HttpRequestMessage?request = state.RequestMessage; SafeWinHttpHandle? requestHandle = state.RequestHandle; Debug.Assert(state.Handler != null); CookieContainer?cookieContainer = state.Handler.CookieContainer; Debug.Assert(state.Handler.CookieUsePolicy == CookieUsePolicy.UseSpecifiedCookieContainer); Debug.Assert(request != null); Debug.Assert(requestHandle != null); Debug.Assert(cookieContainer != null); Debug.Assert(request.RequestUri != null); // Get 'Set-Cookie' headers from response. char[]? buffer = null; uint index = 0; string?cookieHeader; while (WinHttpResponseParser.GetResponseHeader( requestHandle, Interop.WinHttp.WINHTTP_QUERY_SET_COOKIE, ref buffer, ref index, out cookieHeader)) { try { cookieContainer.SetCookies(request.RequestUri, cookieHeader); if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(cookieContainer, $"Added cookie: {cookieHeader}"); } } catch (CookieException) { // We ignore malformed cookies in the response. if (NetEventSource.Log.IsEnabled()) { NetEventSource.Error(cookieContainer, $"Ignoring invalid cookie: {cookieHeader}"); } } } }
public static void ResetCookieRequestHeaders(WinHttpRequestState state, Uri redirectUri) { SafeWinHttpHandle?requestHandle = state.RequestHandle; Debug.Assert(state.Handler != null); Debug.Assert(state.Handler.CookieUsePolicy == CookieUsePolicy.UseSpecifiedCookieContainer); Debug.Assert(state.Handler.CookieContainer != null); Debug.Assert(requestHandle != null); // Clear cookies. if (!Interop.WinHttp.WinHttpAddRequestHeaders( requestHandle, CookieHeaderNameWithColon, (uint)CookieHeaderNameWithColon.Length, Interop.WinHttp.WINHTTP_ADDREQ_FLAG_REPLACE)) { int lastError = Marshal.GetLastWin32Error(); if (lastError != Interop.WinHttp.ERROR_WINHTTP_HEADER_NOT_FOUND) { throw WinHttpException.CreateExceptionUsingError(lastError, nameof(Interop.WinHttp.WinHttpAddRequestHeaders)); } } // Re-add cookies. The GetCookieHeader() method will return the correct set of // cookies based on the redirectUri. string?cookieHeader = GetCookieHeader(redirectUri, state.Handler.CookieContainer); if (!string.IsNullOrEmpty(cookieHeader)) { if (!Interop.WinHttp.WinHttpAddRequestHeaders( requestHandle, cookieHeader, (uint)cookieHeader.Length, Interop.WinHttp.WINHTTP_ADDREQ_FLAG_ADD)) { WinHttpException.ThrowExceptionUsingLastError(nameof(Interop.WinHttp.WinHttpAddRequestHeaders)); } } }
public bool GetProxyForUrl( SafeWinHttpHandle?sessionHandle, Uri uri, out Interop.WinHttp.WINHTTP_PROXY_INFO proxyInfo) { proxyInfo.AccessType = Interop.WinHttp.WINHTTP_ACCESS_TYPE_NO_PROXY; proxyInfo.Proxy = IntPtr.Zero; proxyInfo.ProxyBypass = IntPtr.Zero; if (!_useProxy) { return(false); } bool useProxy = false; Interop.WinHttp.WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions; autoProxyOptions.AutoConfigUrl = AutoConfigUrl; autoProxyOptions.AutoDetectFlags = AutoDetect ? (Interop.WinHttp.WINHTTP_AUTO_DETECT_TYPE_DHCP | Interop.WinHttp.WINHTTP_AUTO_DETECT_TYPE_DNS_A) : 0; autoProxyOptions.AutoLoginIfChallenged = false; autoProxyOptions.Flags = (AutoDetect ? Interop.WinHttp.WINHTTP_AUTOPROXY_AUTO_DETECT : 0) | (!string.IsNullOrEmpty(AutoConfigUrl) ? Interop.WinHttp.WINHTTP_AUTOPROXY_CONFIG_URL : 0); autoProxyOptions.Reserved1 = IntPtr.Zero; autoProxyOptions.Reserved2 = 0; // AutoProxy Cache. // https://docs.microsoft.com/en-us/windows/desktop/WinHttp/autoproxy-cache // If the out-of-process service is active when WinHttpGetProxyForUrl is called, the cached autoproxy // URL and script are available to the whole computer. However, if the out-of-process service is used, // and the fAutoLogonIfChallenged flag in the pAutoProxyOptions structure is true, then the autoproxy // URL and script are not cached. Therefore, calling WinHttpGetProxyForUrl with the fAutoLogonIfChallenged // member set to TRUE results in additional overhead operations that may affect performance. // The following steps can be used to improve performance: // 1. Call WinHttpGetProxyForUrl with the fAutoLogonIfChallenged parameter set to false. The autoproxy // URL and script are cached for future calls to WinHttpGetProxyForUrl. // 2. If Step 1 fails, with ERROR_WINHTTP_LOGIN_FAILURE, then call WinHttpGetProxyForUrl with the // fAutoLogonIfChallenged member set to TRUE. // // We match behavior of WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY and ignore errors. #pragma warning disable CA1845 // file is shared with a build that lacks string.Concat for spans // Underlying code does not understand WebSockets so we need to convert it to http or https. string destination = uri.AbsoluteUri; if (uri.Scheme == UriScheme.Wss) { destination = UriScheme.Https + destination.Substring(UriScheme.Wss.Length); } else if (uri.Scheme == UriScheme.Ws) { destination = UriScheme.Http + destination.Substring(UriScheme.Ws.Length); } #pragma warning restore CA1845 var repeat = false; do { _autoDetectionFailed = false; if (Interop.WinHttp.WinHttpGetProxyForUrl( sessionHandle !, destination, ref autoProxyOptions, out proxyInfo)) { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, "Using autoconfig proxy settings"); } useProxy = true; break; } else { var lastError = Marshal.GetLastWin32Error(); if (NetEventSource.Log.IsEnabled()) { NetEventSource.Error(this, $"error={lastError}"); } if (lastError == Interop.WinHttp.ERROR_WINHTTP_LOGIN_FAILURE) { if (repeat) { // We don't retry more than once. break; } else { repeat = true; autoProxyOptions.AutoLoginIfChallenged = true; } } else { if (lastError == Interop.WinHttp.ERROR_WINHTTP_AUTODETECTION_FAILED) { _autoDetectionFailed = true; _lastTimeAutoDetectionFailed = Environment.TickCount; } break; } } } while (repeat);
private HttpWindowsProxy(WinInetProxyHelper proxyHelper, SafeWinHttpHandle?sessionHandle) { _proxyHelper = proxyHelper; _sessionHandle = sessionHandle; if (proxyHelper.ManualSettingsUsed) { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(proxyHelper, $"ManualSettingsUsed, {proxyHelper.Proxy}"); } _secureProxy = MultiProxy.Parse(_failedProxies, proxyHelper.Proxy, true); _insecureProxy = MultiProxy.Parse(_failedProxies, proxyHelper.Proxy, false); if (!string.IsNullOrWhiteSpace(proxyHelper.ProxyBypass)) { int idx = 0; string?tmp; // Process bypass list for manual setting. // Initial list size is best guess based on string length assuming each entry is at least 5 characters on average. _bypass = new List <string>(proxyHelper.ProxyBypass.Length / 5); while (idx < proxyHelper.ProxyBypass.Length) { // Strip leading spaces and scheme if any. while (idx < proxyHelper.ProxyBypass.Length && proxyHelper.ProxyBypass[idx] == ' ') { idx += 1; } ; if (string.Compare(proxyHelper.ProxyBypass, idx, "http://", 0, 7, StringComparison.OrdinalIgnoreCase) == 0) { idx += 7; } else if (string.Compare(proxyHelper.ProxyBypass, idx, "https://", 0, 8, StringComparison.OrdinalIgnoreCase) == 0) { idx += 8; } if (idx < proxyHelper.ProxyBypass.Length && proxyHelper.ProxyBypass[idx] == '[') { // Strip [] from IPv6 so we can use IdnHost laster for matching. idx += 1; } int start = idx; while (idx < proxyHelper.ProxyBypass.Length && proxyHelper.ProxyBypass[idx] != ' ' && proxyHelper.ProxyBypass[idx] != ';' && proxyHelper.ProxyBypass[idx] != ']') { idx += 1; } ; if (idx == start) { // Empty string. tmp = null; } else if (string.Compare(proxyHelper.ProxyBypass, start, "<local>", 0, 7, StringComparison.OrdinalIgnoreCase) == 0) { _bypassLocal = true; tmp = null; } else { tmp = proxyHelper.ProxyBypass.Substring(start, idx - start); } // Skip trailing characters if any. if (idx < proxyHelper.ProxyBypass.Length && proxyHelper.ProxyBypass[idx] != ';') { // Got stopped at space or ']'. Strip until next ';' or end. while (idx < proxyHelper.ProxyBypass.Length && proxyHelper.ProxyBypass[idx] != ';') { idx += 1; } ; } if (idx < proxyHelper.ProxyBypass.Length && proxyHelper.ProxyBypass[idx] == ';') { idx++; } if (tmp == null) { continue; } _bypass.Add(tmp); } if (_bypass.Count == 0) { // Bypass string only had garbage we did not parse. _bypass = null; } } if (_bypassLocal) { _localIp = new List <IPAddress>(); foreach (NetworkInterface netInterface in NetworkInterface.GetAllNetworkInterfaces()) { IPInterfaceProperties ipProps = netInterface.GetIPProperties(); foreach (UnicastIPAddressInformation addr in ipProps.UnicastAddresses) { _localIp.Add(addr.Address); } } } } }
public static HttpResponseMessage CreateResponseMessage( WinHttpRequestState state, DecompressionMethods manuallyProcessedDecompressionMethods) { HttpRequestMessage?request = state.RequestMessage; SafeWinHttpHandle? requestHandle = state.RequestHandle; Debug.Assert(request != null); Debug.Assert(requestHandle != null); var response = new HttpResponseMessage(); bool stripEncodingHeaders = false; // Create a single buffer to use for all subsequent WinHttpQueryHeaders string interop calls. // This buffer is the length needed for WINHTTP_QUERY_RAW_HEADERS_CRLF, which includes the status line // and all headers separated by CRLF, so it should be large enough for any individual status line or header queries. int bufferLength = GetResponseHeaderCharBufferLength(requestHandle, isTrailingHeaders: false); char[] buffer = ArrayPool <char> .Shared.Rent(bufferLength); try { // Get HTTP version, status code, reason phrase from the response headers. if (IsResponseHttp2(requestHandle)) { response.Version = WinHttpHandler.HttpVersion20; } else { int versionLength = GetResponseHeader(requestHandle, Interop.WinHttp.WINHTTP_QUERY_VERSION, buffer); response.Version = CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase("HTTP/1.1", buffer, 0, versionLength) ? HttpVersion.Version11 : CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase("HTTP/1.0", buffer, 0, versionLength) ? HttpVersion.Version10 : WinHttpHandler.HttpVersionUnknown; } response.StatusCode = (HttpStatusCode)GetResponseHeaderNumberInfo( requestHandle, Interop.WinHttp.WINHTTP_QUERY_STATUS_CODE); int reasonPhraseLength = GetResponseHeader(requestHandle, Interop.WinHttp.WINHTTP_QUERY_STATUS_TEXT, buffer); response.ReasonPhrase = reasonPhraseLength > 0 ? GetReasonPhrase(response.StatusCode, buffer, reasonPhraseLength) : string.Empty; // Create response stream and wrap it in a StreamContent object. var responseStream = new WinHttpResponseStream(requestHandle, state, response); state.RequestHandle = null; // ownership successfully transfered to WinHttpResponseStram. Stream decompressedStream = responseStream; if (manuallyProcessedDecompressionMethods != DecompressionMethods.None) { int contentEncodingStartIndex = 0; int contentEncodingLength = GetResponseHeader( requestHandle, Interop.WinHttp.WINHTTP_QUERY_CONTENT_ENCODING, buffer); CharArrayHelpers.Trim(buffer, ref contentEncodingStartIndex, ref contentEncodingLength); if (contentEncodingLength > 0) { if ((manuallyProcessedDecompressionMethods & DecompressionMethods.GZip) == DecompressionMethods.GZip && CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase(EncodingNameGzip, buffer, contentEncodingStartIndex, contentEncodingLength)) { decompressedStream = new GZipStream(responseStream, CompressionMode.Decompress); stripEncodingHeaders = true; } else if ((manuallyProcessedDecompressionMethods & DecompressionMethods.Deflate) == DecompressionMethods.Deflate && CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase(EncodingNameDeflate, buffer, contentEncodingStartIndex, contentEncodingLength)) { decompressedStream = new DeflateStream(responseStream, CompressionMode.Decompress); stripEncodingHeaders = true; } } } response.Content = new NoWriteNoSeekStreamContent(decompressedStream); response.RequestMessage = request; // Parse raw response headers and place them into response message. ParseResponseHeaders(requestHandle, response, buffer, stripEncodingHeaders); if (response.RequestMessage.Method != HttpMethod.Head) { state.ExpectedBytesToRead = response.Content.Headers.ContentLength; } return(response); } finally { ArrayPool <char> .Shared.Return(buffer); } }
private HttpRequest CreateRequest( Uri uri, string method, Dictionary <string, List <string> >?headers = null, Dictionary <string, string>?cookies = null, AuthenticationInfo?authentication = null, byte[]?body = null) { if (disposed) { throw new ObjectDisposedException("HttpWebRequestClient"); } lock (this) { if (this.sessionHandle == null) { if (this.proxy.HasValue && this.proxy.Value.ProxyType == ProxyType.System && GetProxyForUrl(uri, out string?proxy, out string?bypass) && proxy != null) { sessionHandle = WinHttpOpen( IntPtr.Zero, WINHTTP_ACCESS_TYPE_NAMED_PROXY, proxy, bypass, 0); } else if (this.proxy.HasValue && this.proxy.Value.ProxyType == ProxyType.Custom) { sessionHandle = WinHttpOpen( IntPtr.Zero, WINHTTP_ACCESS_TYPE_NAMED_PROXY, "http://" + this.proxy.Value.Host + ":" + this.proxy.Value.Port, WINHTTP_NO_PROXY_BYPASS, 0); } else { sessionHandle = WinHttpOpen( IntPtr.Zero, WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); } if (sessionHandle.IsInvalid) { throw new IOException(nameof(sessionHandle) + " " + Marshal.GetLastWin32Error()); } uint optionData = 100; if (!WinHttpSetOption(sessionHandle, WINHTTP_OPTION_MAX_CONNS_PER_SERVER, ref optionData)) { Log.Debug("Unable to set connection limit"); } //const uint WINHTTP_FLAG_SECURE_PROTOCOL_SSL2 = 0x00000008; //const uint WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 = 0x00000020; //const uint WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 = 0x00000080; //const uint WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 = 0x00000200; //const uint WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 = 0x00000800; //const uint WINHTTP_FLAG_SECURE_PROTOCOL_ALL = (WINHTTP_FLAG_SECURE_PROTOCOL_SSL2 | WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1); var proto = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2; if (!WinHttpSetOption(sessionHandle, WINHTTP_OPTION_SECURE_PROTOCOLS, ref proto)) { Log.Debug("WINHTTP_OPTION_SECURE_PROTOCOLS not set: " + Marshal.GetLastWin32Error()); } var timeout = (int)Timeout.TotalMilliseconds; if (!WinHttpSetTimeouts(sessionHandle, timeout, timeout, timeout, timeout)) { Log.Debug("Set WinHttpSetTimeouts failed: " + Marshal.GetLastWin32Error()); } } } var hConnect = WinHttpConnect(sessionHandle, uri.Host, (ushort)uri.Port, 0); if (hConnect.IsInvalid) { throw new IOException(nameof(hConnect)); } var hRequest = WinHttpOpenRequest(hConnect, method, uri.PathAndQuery, null, null, null, uri.Scheme == "https" ? WINHTTP_FLAG_ESCAPE_DISABLE | WINHTTP_FLAG_SECURE : WINHTTP_FLAG_ESCAPE_DISABLE); if (hRequest.IsInvalid) { throw new IOException(nameof(hRequest)); } var dwFlags = SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE | SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID; if (!WinHttpSetOption( hRequest, WINHTTP_OPTION_SECURITY_FLAGS, ref dwFlags)) { Log.Debug("Ignore cert error: " + Marshal.GetLastWin32Error()); } else { Log.Debug("Ignore cert error config set"); } if (authentication.HasValue && !string.IsNullOrEmpty(authentication.Value.UserName)) { if (!WinHttpSetCredentials(hRequest, WINHTTP_AUTH_TARGET_SERVER, WINHTTP_AUTH_SCHEME_BASIC, authentication.Value.UserName, authentication.Value.Password, IntPtr.Zero)) { Log.Debug("Error WinHttpSetCredentials - server: " + Marshal.GetLastWin32Error()); } } if (this.proxy.HasValue && !string.IsNullOrEmpty(this.proxy.Value.UserName)) { if (!WinHttpSetCredentials(hRequest, WINHTTP_AUTH_TARGET_PROXY, WINHTTP_AUTH_SCHEME_BASIC, authentication.Value.UserName, authentication.Value.Password, IntPtr.Zero)) { Log.Debug("Error WinHttpSetCredentials - proxy: " + Marshal.GetLastWin32Error()); } } //dwFlags = WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1; //if (!WinHttpSetOption( // hConnect, // WINHTTP_OPTION_SECURE_PROTOCOLS, // ref dwFlags)) //{ // Log.Debug("WINHTTP_OPTION_SECURE_PROTOCOLS: " + Marshal.GetLastWin32Error()); //} //else //{ // Log.Debug("WINHTTP_OPTION_SECURE_PROTOCOLS set"); //} return(new HttpRequest { Session = new WinHttpSession(uri, hConnect, hRequest, headers, cookies) }); }
public static extern bool WinHttpGetProxyForUrl( SafeWinHttpHandle?sessionHandle, string url, ref WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions, out WINHTTP_PROXY_INFO proxyInfo);
// TODO: [DllImportGenerator] Switch to use GeneratedDllImport once we support non-blittable structs. [return: MarshalAs(UnmanagedType.Bool)]public static extern bool WinHttpGetProxyForUrl( SafeWinHttpHandle? sessionHandle, string url, ref WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions, out WINHTTP_PROXY_INFO proxyInfo);