+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-internal static partial class Interop
-{
- internal static partial class Http
- {
- // Enum for constants defined for the enum CURLcode in curl.h
- internal enum CURLcode
- {
- CURLE_OK = 0,
- CURLE_UNSUPPORTED_PROTOCOL = 1,
- CURLE_FAILED_INIT = 2,
- CURLE_NOT_BUILT_IN = 4,
- CURLE_COULDNT_RESOLVE_HOST = 6,
- CURLE_OUT_OF_MEMORY = 27,
- CURLE_OPERATION_TIMEDOUT = 28,
- CURLE_ABORTED_BY_CALLBACK = 42,
- CURLE_UNKNOWN_OPTION = 48,
- CURLE_RECV_ERROR = 56,
- CURLE_SEND_FAIL_REWIND = 65
- }
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Runtime.InteropServices;
-
-internal static partial class Interop
-{
- internal static partial class Http
- {
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_EasyCreate")]
- public static extern SafeCurlHandle EasyCreate();
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_EasyDestroy")]
- private static extern void EasyDestroy(IntPtr handle);
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_EasySetOptionString", CharSet = CharSet.Ansi)]
- public static extern CURLcode EasySetOptionString(SafeCurlHandle curl, CURLoption option, string value);
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_EasySetOptionLong")]
- public static extern CURLcode EasySetOptionLong(SafeCurlHandle curl, CURLoption option, long value);
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_EasySetOptionPointer")]
- public static extern CURLcode EasySetOptionPointer(SafeCurlHandle curl, CURLoption option, IntPtr value);
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_EasySetOptionPointer")]
- public static extern CURLcode EasySetOptionPointer(SafeCurlHandle curl, CURLoption option, SafeHandle value);
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_EasyGetErrorString")]
- public static extern IntPtr EasyGetErrorString(int code);
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_EasyGetInfoPointer")]
- public static extern CURLcode EasyGetInfoPointer(IntPtr handle, CURLINFO info, out IntPtr value);
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_EasyGetInfoPointer")]
- public static extern CURLcode EasyGetInfoPointer(SafeCurlHandle handle, CURLINFO info, out IntPtr value);
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_EasyGetInfoLong")]
- public static extern CURLcode EasyGetInfoLong(SafeCurlHandle handle, CURLINFO info, out long value);
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_EasyPerform")]
- public static extern CURLcode EasyPerform(SafeCurlHandle curl);
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_EasyUnpause")]
- public static extern CURLcode EasyUnpause(SafeCurlHandle easy);
-
- public delegate CurlSeekResult SeekCallback(IntPtr userPointer, long offset, int origin);
-
- public delegate ulong ReadWriteCallback(IntPtr buffer, ulong bufferSize, ulong nitems, IntPtr userPointer);
-
- public delegate CURLcode SslCtxCallback(IntPtr curl, IntPtr sslCtx, IntPtr userPointer);
-
- public delegate void DebugCallback(IntPtr curl, CurlInfoType type, IntPtr data, ulong size, IntPtr userPointer);
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_RegisterSeekCallback")]
- public static extern void RegisterSeekCallback(
- SafeCurlHandle curl,
- SeekCallback callback,
- IntPtr userPointer,
- ref SafeCallbackHandle callbackHandle);
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_RegisterReadWriteCallback")]
- public static extern void RegisterReadWriteCallback(
- SafeCurlHandle curl,
- ReadWriteFunction functionType,
- ReadWriteCallback callback,
- IntPtr userPointer,
- ref SafeCallbackHandle callbackHandle);
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_RegisterSslCtxCallback")]
- public static extern CURLcode RegisterSslCtxCallback(
- SafeCurlHandle curl,
- SslCtxCallback callback,
- IntPtr userPointer,
- ref SafeCallbackHandle callbackHandle);
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_RegisterDebugCallback")]
- public static extern CURLcode RegisterDebugCallback(
- SafeCurlHandle curl,
- DebugCallback callback,
- IntPtr userPointer,
- ref SafeCallbackHandle callbackHandle);
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_FreeCallbackHandle")]
- private static extern void FreeCallbackHandle(IntPtr handle);
-
- // Curl options are of the format <type base> + <n>
- private const int CurlOptionLongBase = 0;
- private const int CurlOptionObjectPointBase = 10000;
- private const int CurlOptionOffTBase = 30000;
-
- // Enum for constants defined for the enum CURLoption in curl.h
- internal enum CURLoption
- {
- CURLOPT_INFILESIZE = CurlOptionLongBase + 14,
- CURLOPT_SSLVERSION = CurlOptionLongBase + 32,
- CURLOPT_VERBOSE = CurlOptionLongBase + 41,
- CURLOPT_NOBODY = CurlOptionLongBase + 44,
- CURLOPT_UPLOAD = CurlOptionLongBase + 46,
- CURLOPT_POST = CurlOptionLongBase + 47,
- CURLOPT_FOLLOWLOCATION = CurlOptionLongBase + 52,
- CURLOPT_PROXYPORT = CurlOptionLongBase + 59,
- CURLOPT_POSTFIELDSIZE = CurlOptionLongBase + 60,
- CURLOPT_SSL_VERIFYPEER = CurlOptionLongBase + 64,
- CURLOPT_MAXREDIRS = CurlOptionLongBase + 68,
- CURLOPT_SSL_VERIFYHOST = CurlOptionLongBase + 81,
- CURLOPT_HTTP_VERSION = CurlOptionLongBase + 84,
- CURLOPT_DNS_CACHE_TIMEOUT = CurlOptionLongBase + 92,
- CURLOPT_NOSIGNAL = CurlOptionLongBase + 99,
- CURLOPT_PROXYTYPE = CurlOptionLongBase + 101,
- CURLOPT_HTTPAUTH = CurlOptionLongBase + 107,
- CURLOPT_TCP_NODELAY = CurlOptionLongBase + 121,
- CURLOPT_TCP_KEEPALIVE = CurlOptionLongBase + 213,
- CURLOPT_CONNECTTIMEOUT_MS = CurlOptionLongBase + 156,
- CURLOPT_ADDRESS_SCOPE = CurlOptionLongBase + 171,
- CURLOPT_PROTOCOLS = CurlOptionLongBase + 181,
- CURLOPT_REDIR_PROTOCOLS = CurlOptionLongBase + 182,
-
- CURLOPT_URL = CurlOptionObjectPointBase + 2,
- CURLOPT_PROXY = CurlOptionObjectPointBase + 4,
- CURLOPT_PROXYUSERPWD = CurlOptionObjectPointBase + 6,
- CURLOPT_COOKIE = CurlOptionObjectPointBase + 22,
- CURLOPT_HTTPHEADER = CurlOptionObjectPointBase + 23,
- CURLOPT_CUSTOMREQUEST = CurlOptionObjectPointBase + 36,
- CURLOPT_ACCEPT_ENCODING = CurlOptionObjectPointBase + 102,
- CURLOPT_PRIVATE = CurlOptionObjectPointBase + 103,
- CURLOPT_COPYPOSTFIELDS = CurlOptionObjectPointBase + 165,
- CURLOPT_USERNAME = CurlOptionObjectPointBase + 173,
- CURLOPT_PASSWORD = CurlOptionObjectPointBase + 174,
- CURLOPT_CAPATH = CurlOptionObjectPointBase + 97,
- CURLOPT_PROXY_CAPATH = CurlOptionObjectPointBase + 247,
- CURLOPT_CAINFO = CurlOptionObjectPointBase + 65,
- CURLOPT_PROXY_CAINFO = CurlOptionObjectPointBase + 246,
-
- CURLOPT_INFILESIZE_LARGE = CurlOptionOffTBase + 115,
- CURLOPT_POSTFIELDSIZE_LARGE = CurlOptionOffTBase + 120,
- }
-
- internal enum ReadWriteFunction
- {
- Write = 0,
- Read = 1,
- Header = 2,
- }
-
- // Curl info are of the format <type base> + <n>
- private const int CurlInfoStringBase = 0x100000;
- private const int CurlInfoLongBase = 0x200000;
-
- // Enum for constants defined for CURL_HTTP_VERSION
- internal enum CurlHttpVersion
- {
- CURL_HTTP_VERSION_NONE = 0,
- CURL_HTTP_VERSION_1_0 = 1,
- CURL_HTTP_VERSION_1_1 = 2,
- CURL_HTTP_VERSION_2TLS = 4,
- };
-
- // Enum for constants defined for CURL_SSLVERSION
- internal enum CurlSslVersion
- {
- CURL_SSLVERSION_TLSv1 = 1, /* TLS 1.x */
- CURL_SSLVERSION_SSLv2 = 2, /* SSL 2 */
- CURL_SSLVERSION_SSLv3 = 3, /* SSL 3 */
- CURL_SSLVERSION_TLSv1_0 = 4, /* TLS 1.0 */
- CURL_SSLVERSION_TLSv1_1 = 5, /* TLS 1.1 */
- CURL_SSLVERSION_TLSv1_2 = 6, /* TLS 1.2 */
- CURL_SSLVERSION_TLSv1_3 = 7, /* TLS 1.3 */
- };
-
- // Enum for constants defined for the enum CURLINFO in curl.h
- internal enum CURLINFO
- {
- CURLINFO_EFFECTIVE_URL = CurlInfoStringBase + 1,
- CURLINFO_PRIVATE = CurlInfoStringBase + 21,
- CURLINFO_HTTPAUTH_AVAIL = CurlInfoLongBase + 23,
- }
-
- // AUTH related constants
- [Flags]
- internal enum CURLAUTH
- {
- None = 0,
- Basic = 1 << 0,
- Digest = 1 << 1,
- Negotiate = 1 << 2,
- NTLM = 1 << 3,
- }
-
- // Enum for constants defined for the enum curl_proxytype in curl.h
- internal enum curl_proxytype
- {
- CURLPROXY_HTTP = 0,
- }
-
- [Flags]
- internal enum CurlProtocols
- {
- CURLPROTO_HTTP = (1 << 0),
- CURLPROTO_HTTPS = (1 << 1),
- }
-
- // Enum for constants defined for the results of CURL_SEEKFUNCTION
- internal enum CurlSeekResult : int
- {
- CURL_SEEKFUNC_OK = 0,
- CURL_SEEKFUNC_FAIL = 1,
- CURL_SEEKFUNC_CANTSEEK = 2,
- }
-
- internal enum CurlInfoType : int
- {
- CURLINFO_TEXT = 0,
- CURLINFO_HEADER_IN = 1,
- CURLINFO_HEADER_OUT = 2,
- CURLINFO_DATA_IN = 3,
- CURLINFO_DATA_OUT = 4,
- CURLINFO_SSL_DATA_IN = 5,
- CURLINFO_SSL_DATA_OUT = 6,
- };
-
- // constants defined for the results of a CURL_READ or CURL_WRITE function
- internal const ulong CURL_READFUNC_ABORT = 0x10000000;
- internal const ulong CURL_READFUNC_PAUSE = 0x10000001;
- internal const ulong CURL_WRITEFUNC_PAUSE = 0x10000001;
-
- internal const ulong CURL_MAX_HTTP_HEADER = 100 * 1024;
-
- internal sealed class SafeCurlHandle : SafeHandle
- {
- public SafeCurlHandle() : base(IntPtr.Zero, true)
- {
- }
-
- public override bool IsInvalid
- {
- get { return handle == IntPtr.Zero; }
- }
-
- protected override bool ReleaseHandle()
- {
- EasyDestroy(handle);
- SetHandle(IntPtr.Zero);
- return true;
- }
- }
-
- internal sealed class SafeCallbackHandle : SafeHandle
- {
- public SafeCallbackHandle()
- : base(IntPtr.Zero, true)
- {
- }
-
- public override bool IsInvalid
- {
- get { return handle == IntPtr.Zero; }
- }
-
- protected override bool ReleaseHandle()
- {
- FreeCallbackHandle(handle);
- SetHandle(IntPtr.Zero);
- return true;
- }
- }
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Runtime.InteropServices;
-
-internal static partial class Interop
-{
- // Initialization of libcurl is done in a static constructor.
- // This enables a project simply to include this file, and any usage of any of
- // the Http functions will trigger initialization.
-
- internal static partial class Http
- {
- static Http()
- {
- HttpInitializer.Initialize();
- }
- }
-
- internal static class HttpInitializer
- {
- static HttpInitializer()
- {
-#if !SYSNETHTTP_NO_OPENSSL
- string opensslVersion = Interop.Http.GetSslVersionDescription();
- if (string.IsNullOrEmpty(opensslVersion) ||
- opensslVersion.IndexOf(Interop.Http.OpenSslDescriptionPrefix, StringComparison.OrdinalIgnoreCase) != -1)
- {
- // CURL uses OpenSSL which we must initialize first to guarantee thread-safety.
- // We'll wake up whatever OpenSSL we're going to run against, but might later determine that
- // they aren't compatible.
- CryptoInitializer.Initialize();
- }
-#endif
-
- if (EnsureCurlIsInitialized() != 0)
- {
- throw new InvalidOperationException();
- }
- }
-
- internal static void Initialize()
- {
- // No-op that exists to provide a hook for other static constructors
- }
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_EnsureCurlIsInitialized")]
- private static extern int EnsureCurlIsInitialized();
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Runtime.InteropServices;
-using Microsoft.Win32.SafeHandles;
-
-internal static partial class Interop
-{
- internal static partial class Http
- {
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_MultiCreate")]
- public static extern SafeCurlMultiHandle MultiCreate();
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_MultiDestroy")]
- private static extern CURLMcode MultiDestroy(IntPtr handle);
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_MultiAddHandle")]
- public static extern CURLMcode MultiAddHandle(SafeCurlMultiHandle multiHandle, SafeCurlHandle easyHandle);
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_MultiRemoveHandle")]
- public static extern CURLMcode MultiRemoveHandle(SafeCurlMultiHandle multiHandle, SafeCurlHandle easyHandle);
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_MultiWait")]
- public static extern CURLMcode MultiWait(
- SafeCurlMultiHandle multiHandle,
- SafeFileHandle extraFileDescriptor,
- out bool isExtraFileDescriptorActive,
- out bool isTimeout);
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_MultiPerform")]
- public static extern CURLMcode MultiPerform(SafeCurlMultiHandle multiHandle);
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_MultiInfoRead")]
- public static extern bool MultiInfoRead(
- SafeCurlMultiHandle multiHandle,
- out CURLMSG message,
- out IntPtr easyHandle,
- out CURLcode result);
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_MultiGetErrorString")]
- public static extern IntPtr MultiGetErrorString(int code);
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_MultiSetOptionLong")]
- public static extern CURLMcode MultiSetOptionLong(SafeCurlMultiHandle curl, CURLMoption option, long value);
-
- // Enum for constants defined for the enum CURLMcode in multi.h
- internal enum CURLMcode : int
- {
- CURLM_CALL_MULTI_PERFORM = -1,
- CURLM_OK = 0,
- CURLM_BAD_HANDLE = 1,
- CURLM_BAD_EASY_HANDLE = 2,
- CURLM_OUT_OF_MEMORY = 3,
- CURLM_INTERNAL_ERROR = 4,
- CURLM_BAD_SOCKET = 5,
- CURLM_UNKNOWN_OPTION = 6,
- CURLM_ADDED_ALREADY = 7,
- }
-
- internal enum CURLMoption : int
- {
- CURLMOPT_PIPELINING = 3,
- CURLMOPT_MAX_HOST_CONNECTIONS = 7,
- }
-
- internal enum CurlPipe : int
- {
- CURLPIPE_MULTIPLEX = 2
- }
-
- // Enum for constants defined for the enum CURLMSG in multi.h
- internal enum CURLMSG : int
- {
- CURLMSG_DONE = 1,
- }
-
- internal sealed class SafeCurlMultiHandle : SafeHandle
- {
- public SafeCurlMultiHandle()
- : base(IntPtr.Zero, true)
- {
- }
-
- public override bool IsInvalid
- {
- get { return this.handle == IntPtr.Zero; }
- }
-
- protected override bool ReleaseHandle()
- {
- bool result = MultiDestroy(handle) == CURLMcode.CURLM_OK;
- SetHandle(IntPtr.Zero);
-
-#if !SYSNETHTTP_NO_OPENSSL
- Interop.Crypto.ErrClearError(); // Ensure that no SSL errors were left on the queue by libcurl.
-#endif
-
- return result;
- }
- }
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Runtime.InteropServices;
-
-internal static partial class Interop
-{
- internal static partial class Http
- {
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_SListAppend", CharSet = CharSet.Ansi)]
- private static extern IntPtr SListAppend(IntPtr slist, string headerValue);
-
- internal static bool SListAppend(SafeCurlSListHandle slist, string headerValue)
- {
- bool gotRef = false;
- try
- {
- slist.DangerousAddRef(ref gotRef);
- IntPtr newHandle = SListAppend(slist.DangerousGetHandle(), headerValue);
- if (newHandle != IntPtr.Zero)
- {
- slist.SetHandle(newHandle);
- return true;
- }
- return false;
- }
- finally
- {
- if (gotRef)
- slist.DangerousRelease();
- }
- }
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_SListFreeAll")]
- private static extern void SListFreeAll(IntPtr slist);
-
- internal sealed class SafeCurlSListHandle : SafeHandle
- {
- public SafeCurlSListHandle() : base(IntPtr.Zero, true)
- {
- }
-
- public override bool IsInvalid
- {
- get { return handle == IntPtr.Zero; }
- }
-
- public new void SetHandle(IntPtr newHandle)
- {
- base.SetHandle(newHandle);
- }
-
- protected override bool ReleaseHandle()
- {
- SListFreeAll(handle);
- SetHandle(IntPtr.Zero);
- return true;
- }
- }
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-
-internal static partial class Interop
-{
- internal static partial class Http
- {
- [Flags]
- internal enum CurlFeatures : int
- {
- CURL_VERSION_IPV6 = (1 << 0),
- CURL_VERSION_KERBEROS4 = (1 << 1),
- CURL_VERSION_SSL = (1 << 2),
- CURL_VERSION_LIBZ = (1 << 3),
- CURL_VERSION_NTLM = (1 << 4),
- CURL_VERSION_GSSNEGOTIATE = (1 << 5),
- CURL_VERSION_DEBUG = (1 << 6),
- CURL_VERSION_ASYNCHDNS = (1 << 7),
- CURL_VERSION_SPNEGO = (1 << 8),
- CURL_VERSION_LARGEFILE = (1 << 9),
- CURL_VERSION_IDN = (1 << 10),
- CURL_VERSION_SSPI = (1 << 11),
- CURL_VERSION_CONV = (1 << 12),
- CURL_VERSION_CURLDEBUG = (1 << 13),
- CURL_VERSION_TLSAUTH_SRP = (1 << 14),
- CURL_VERSION_NTLM_WB = (1 << 15),
- CURL_VERSION_HTTP2 = (1 << 16),
- CURL_VERSION_GSSAPI = (1 << 17),
- CURL_VERSION_KERBEROS5 = (1 << 18),
- CURL_VERSION_UNIX_SOCKETS = (1 << 19),
- CURL_VERSION_PSL = (1 << 20),
- };
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_GetSupportedFeatures")]
- internal static extern CurlFeatures GetSupportedFeatures();
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_GetSupportsHttp2Multiplexing")]
- internal static extern bool GetSupportsHttp2Multiplexing();
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_GetVersionDescription")]
- internal static extern string GetVersionDescription();
-
- [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_GetSslVersionDescription")]
- internal static extern string GetSslVersionDescription();
-
- internal const string OpenSslDescriptionPrefix = "OpenSSL/";
- internal const string SecureTransportDescription = "SecureTransport";
- internal const string LibreSslDescription = "LibreSSL";
-
-#if !SYSNETHTTP_NO_OPENSSL
- private static readonly Lazy<string> s_requiredOpenSslDescription =
- new Lazy<string>(() => DetermineRequiredOpenSslDescription());
-
- private static readonly Lazy<bool> s_hasMatchingOpenSsl =
- new Lazy<bool>(() => RequiredOpenSslDescription == GetSslVersionDescription());
-
- internal static string RequiredOpenSslDescription => s_requiredOpenSslDescription.Value;
- internal static bool HasMatchingOpenSslVersion => s_hasMatchingOpenSsl.Value;
-
- private static string DetermineRequiredOpenSslDescription()
- {
- long ver = Interop.OpenSsl.OpenSslVersionNumber();
-
- // OpenSSL version numbers are encoded as
- // 0xMNNFFPPS: major (one nybble), minor (one byte, unaligned),
- // "fix" (one byte, unaligned), patch (one byte, unaligned), status (one nybble)
- //
- // e.g. 1.0.2a final is 0x1000201F
- //
- // libcurl's OpenSSL vtls backend ignores status in the version string.
- // Major, minor, and fix are encoded (by libcurl) as unpadded hex
- // (0 => "0", 15 => "f", 16 => "10").
- //
- // Patch is encoded as in the way OpenSSL would do it.
- // 0x00 => ""
- // 0x01 => "a"
- // 0x1a (26) => "z"
- // 0x1b (27) => "za"
- // 0x34 (52) => "zz"
- // 0x35 (53) should probably be "zza", but it never came up, and is not currently
- // handled correctly by libcurl (which would call it "z{").
-
- byte patchValue = (byte)((ver & 0xFF0) >> 4);
- string patch = string.Empty;
-
- if (patchValue > 52)
- {
- Debug.Fail($"OpenSSL version ({ver:8X}) patch value ({patchValue}) is out of range");
- throw new InvalidOperationException();
- }
- else if (patchValue > 26)
- {
- Span<char> patchStr = stackalloc char[2];
- patchStr[0] = 'z';
- // backtick is ('a'-1)
- patchStr[1] = (char)('`' + (patchValue - 26));
- patch = new string(patchStr);
- }
- else if (patchValue > 0)
- {
- // backtick is ('a'-1)
- patch = new string((char)('`' + patchValue), 1);
- }
-
- return $"{OpenSslDescriptionPrefix}{(ver >> 28) & 0xF:x}.{(byte)(ver >> 20):x}.{(byte)(ver >> 12):x}{patch}";
- }
-#endif
- }
-}
add_subdirectory(System.Native)
if (NOT CLR_CMAKE_PLATFORM_WASM)
- add_subdirectory(System.Net.Http.Native)
add_subdirectory(System.Net.Security.Native)
add_subdirectory(System.Security.Cryptography.Native)
endif()
+++ /dev/null
-project(System.Net.Http.Native C)
-
-find_package(CURL)
-if(NOT CURL_FOUND)
- message(FATAL_ERROR "!!! Cannot find libcurl and System.Net.Http.Native cannot build without it. Try installing libcurl4-openssl-dev (or the appropriate package for your platform) !!!")
-endif(NOT CURL_FOUND)
-
-if(CMAKE_STATIC_LIB_LINK)
- find_library(CURL_STATIC_LIB NAMES libcurl.a)
- if(NOT CURL_STATIC_LIB)
- message(FATAL_ERROR "!!! Cannot find libcurl static lib and System.Net.Http.Native cannot build without it. Try installing libcurl4-openssl-dev (or the appropriate package for your platform) !!!")
- endif(NOT CURL_STATIC_LIB)
- set(CURL_LIBRARIES ${CURL_STATIC_LIB})
- add_compile_options(-DCURL_STATICLIB)
-endif(CMAKE_STATIC_LIB_LINK)
-
-
-set(NATIVEHTTP_SOURCES
- pal_curlinit.c
- pal_easy.c
- pal_multi.c
- pal_slist.c
- pal_versioninfo.c
-)
-
-include_directories(${CURL_INCLUDE_DIR})
-
-add_library(System.Net.Http.Native
- SHARED
- ${NATIVEHTTP_SOURCES}
- ${VERSION_FILE_PATH}
-)
-
-add_library(System.Net.Http.Native-Static
- STATIC
- ${NATIVEHTTP_SOURCES}
- ${VERSION_FILE_PATH}
-)
-
-# Disable the "lib" prefix and override default name
-set_target_properties(System.Net.Http.Native-Static PROPERTIES PREFIX "")
-set_target_properties(System.Net.Http.Native-Static PROPERTIES OUTPUT_NAME System.Net.Http.Native CLEAN_DIRECT_OUTPUT 1)
-
-target_link_libraries(System.Net.Http.Native
- ${CURL_LIBRARIES}
-)
-
-install_library_and_symbols (System.Net.Http.Native)
-install (TARGETS System.Net.Http.Native-Static DESTINATION .)
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-#include "pal_config.h"
-#include "pal_curlinit.h"
-
-#include <pthread.h>
-#include <stdbool.h>
-#include <curl/curl.h>
-
-int32_t HttpNative_EnsureCurlIsInitialized(void)
-{
- static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
- static bool initializationAttempted = false;
- static int32_t errorCode = -1;
-
- pthread_mutex_lock(&lock);
- {
- if (!initializationAttempted)
- {
- errorCode = (int32_t)(curl_global_init(CURL_GLOBAL_ALL));
- initializationAttempted = true;
- }
- }
- pthread_mutex_unlock(&lock);
-
- return errorCode;
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-#pragma once
-
-#include "pal_types.h"
-#include "pal_compiler.h"
-
-/**
- * Initializes curl.
- *
- * Thread-safe and idempotent. Must be called before using any other curl function.
- * EnsureOpenSSLIsInitialized from System.Security.Cryptography.Native must already
- * have been called before calling this.
- *
- * Returns 0 on success and non-zero on failure.
- */
-DLLEXPORT int32_t HttpNative_EnsureCurlIsInitialized(void);
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-#include "pal_config.h"
-#include "pal_easy.h"
-
-#include <stdlib.h>
-#include <stdbool.h>
-#include <assert.h>
-
-c_static_assert(PAL_CURLOPT_INFILESIZE == CURLOPT_INFILESIZE);
-c_static_assert(PAL_CURLOPT_SSLVERSION == CURLOPT_SSLVERSION);
-c_static_assert(PAL_CURLOPT_VERBOSE == CURLOPT_VERBOSE);
-c_static_assert(PAL_CURLOPT_NOBODY == CURLOPT_NOBODY);
-c_static_assert(PAL_CURLOPT_UPLOAD == CURLOPT_UPLOAD);
-c_static_assert(PAL_CURLOPT_POST == CURLOPT_POST);
-c_static_assert(PAL_CURLOPT_FOLLOWLOCATION == CURLOPT_FOLLOWLOCATION);
-c_static_assert(PAL_CURLOPT_PROXYPORT == CURLOPT_PROXYPORT);
-c_static_assert(PAL_CURLOPT_POSTFIELDSIZE == CURLOPT_POSTFIELDSIZE);
-c_static_assert(PAL_CURLOPT_SSL_VERIFYPEER == CURLOPT_SSL_VERIFYPEER);
-c_static_assert(PAL_CURLOPT_MAXREDIRS == CURLOPT_MAXREDIRS);
-c_static_assert(PAL_CURLOPT_SSL_VERIFYHOST == CURLOPT_SSL_VERIFYHOST);
-c_static_assert(PAL_CURLOPT_HTTP_VERSION == CURLOPT_HTTP_VERSION);
-c_static_assert(PAL_CURLOPT_DNS_CACHE_TIMEOUT == CURLOPT_DNS_CACHE_TIMEOUT);
-c_static_assert(PAL_CURLOPT_NOSIGNAL == CURLOPT_NOSIGNAL);
-c_static_assert(PAL_CURLOPT_PROXYTYPE == CURLOPT_PROXYTYPE);
-c_static_assert(PAL_CURLOPT_HTTPAUTH == CURLOPT_HTTPAUTH);
-c_static_assert(PAL_CURLOPT_TCP_NODELAY == CURLOPT_TCP_NODELAY);
-c_static_assert(PAL_CURLOPT_TCP_KEEPALIVE == CURLOPT_TCP_KEEPALIVE);
-c_static_assert(PAL_CURLOPT_CONNECTTIMEOUT_MS == CURLOPT_CONNECTTIMEOUT_MS);
-c_static_assert(PAL_CURLOPT_ADDRESS_SCOPE == CURLOPT_ADDRESS_SCOPE);
-c_static_assert(PAL_CURLOPT_PROTOCOLS == CURLOPT_PROTOCOLS);
-c_static_assert(PAL_CURLOPT_REDIR_PROTOCOLS == CURLOPT_REDIR_PROTOCOLS);
-
-c_static_assert(PAL_CURLOPT_URL == CURLOPT_URL);
-c_static_assert(PAL_CURLOPT_PROXY == CURLOPT_PROXY);
-c_static_assert(PAL_CURLOPT_PROXYUSERPWD == CURLOPT_PROXYUSERPWD);
-c_static_assert(PAL_CURLOPT_COOKIE == CURLOPT_COOKIE);
-c_static_assert(PAL_CURLOPT_HTTPHEADER == CURLOPT_HTTPHEADER);
-c_static_assert(PAL_CURLOPT_CUSTOMREQUEST == CURLOPT_CUSTOMREQUEST);
-c_static_assert(PAL_CURLOPT_ACCEPT_ENCODING == CURLOPT_ACCEPT_ENCODING);
-c_static_assert(PAL_CURLOPT_PRIVATE == CURLOPT_PRIVATE);
-c_static_assert(PAL_CURLOPT_COPYPOSTFIELDS == CURLOPT_COPYPOSTFIELDS);
-c_static_assert(PAL_CURLOPT_USERNAME == CURLOPT_USERNAME);
-c_static_assert(PAL_CURLOPT_PASSWORD == CURLOPT_PASSWORD);
-c_static_assert(PAL_CURLOPT_CAPATH == CURLOPT_CAPATH);
-#ifdef CURLOPT_PROXY_CAPATH
-c_static_assert(PAL_CURLOPT_PROXY_CAPATH == CURLOPT_PROXY_CAPATH);
-#endif
-c_static_assert(PAL_CURLOPT_CAINFO == CURLOPT_CAINFO);
-#ifdef CURLOPT_PROXY_CAINFO
-c_static_assert(PAL_CURLOPT_PROXY_CAINFO == CURLOPT_PROXY_CAINFO);
-#endif
-
-c_static_assert(PAL_CURLOPT_INFILESIZE_LARGE == CURLOPT_INFILESIZE_LARGE);
-c_static_assert(PAL_CURLOPT_POSTFIELDSIZE_LARGE == CURLOPT_POSTFIELDSIZE_LARGE);
-
-c_static_assert(PAL_CURLE_OK == CURLE_OK);
-c_static_assert(PAL_CURLE_UNSUPPORTED_PROTOCOL == CURLE_UNSUPPORTED_PROTOCOL);
-c_static_assert(PAL_CURLE_FAILED_INIT == CURLE_FAILED_INIT);
-c_static_assert(PAL_CURLE_NOT_BUILT_IN == CURLE_NOT_BUILT_IN);
-c_static_assert(PAL_CURLE_COULDNT_RESOLVE_HOST == CURLE_COULDNT_RESOLVE_HOST);
-c_static_assert(PAL_CURLE_OUT_OF_MEMORY == CURLE_OUT_OF_MEMORY);
-c_static_assert(PAL_CURLE_OPERATION_TIMEDOUT == CURLE_OPERATION_TIMEDOUT);
-c_static_assert(PAL_CURLE_ABORTED_BY_CALLBACK == CURLE_ABORTED_BY_CALLBACK);
-c_static_assert(PAL_CURLE_UNKNOWN_OPTION == CURLE_UNKNOWN_OPTION);
-c_static_assert(PAL_CURLE_RECV_ERROR == CURLE_RECV_ERROR);
-c_static_assert(PAL_CURLE_SEND_FAIL_REWIND == CURLE_SEND_FAIL_REWIND);
-
-c_static_assert(PAL_CURL_HTTP_VERSION_NONE == CURL_HTTP_VERSION_NONE);
-c_static_assert(PAL_CURL_HTTP_VERSION_1_0 == CURL_HTTP_VERSION_1_0);
-c_static_assert(PAL_CURL_HTTP_VERSION_1_1 == CURL_HTTP_VERSION_1_1);
-#if HAVE_CURL_HTTP_VERSION_2TLS
-c_static_assert(PAL_CURL_HTTP_VERSION_2TLS == CURL_HTTP_VERSION_2TLS);
-#endif
-
-c_static_assert(PAL_CURL_SSLVERSION_SSLv2 == CURL_SSLVERSION_SSLv2);
-c_static_assert(PAL_CURL_SSLVERSION_SSLv3 == CURL_SSLVERSION_SSLv3);
-c_static_assert(PAL_CURL_SSLVERSION_TLSv1 == CURL_SSLVERSION_TLSv1);
-#if HAVE_CURL_SSLVERSION_TLSv1_012
-c_static_assert(PAL_CURL_SSLVERSION_TLSv1_0 == CURL_SSLVERSION_TLSv1_0);
-c_static_assert(PAL_CURL_SSLVERSION_TLSv1_1 == CURL_SSLVERSION_TLSv1_1);
-c_static_assert(PAL_CURL_SSLVERSION_TLSv1_2 == CURL_SSLVERSION_TLSv1_2);
-#endif
-
-c_static_assert(PAL_CURLINFO_EFFECTIVE_URL == CURLINFO_EFFECTIVE_URL);
-c_static_assert(PAL_CURLINFO_PRIVATE == CURLINFO_PRIVATE);
-c_static_assert(PAL_CURLINFO_HTTPAUTH_AVAIL == CURLINFO_HTTPAUTH_AVAIL);
-
-c_static_assert(PAL_CURLAUTH_None == CURLAUTH_NONE);
-c_static_assert(PAL_CURLAUTH_Basic == CURLAUTH_BASIC);
-c_static_assert(PAL_CURLAUTH_Digest == CURLAUTH_DIGEST);
-c_static_assert(PAL_CURLAUTH_Negotiate == CURLAUTH_GSSNEGOTIATE);
-c_static_assert(PAL_CURLAUTH_NTLM == CURLAUTH_NTLM);
-
-c_static_assert(PAL_CURLPROXY_HTTP == CURLPROXY_HTTP);
-
-c_static_assert(PAL_CURLPROTO_HTTP == CURLPROTO_HTTP);
-c_static_assert(PAL_CURLPROTO_HTTPS == CURLPROTO_HTTPS);
-
-c_static_assert(PAL_CURL_SEEKFUNC_OK == CURL_SEEKFUNC_OK);
-c_static_assert(PAL_CURL_SEEKFUNC_FAIL == CURL_SEEKFUNC_FAIL);
-c_static_assert(PAL_CURL_SEEKFUNC_CANTSEEK == CURL_SEEKFUNC_CANTSEEK);
-
-c_static_assert(PAL_CURL_READFUNC_ABORT == CURL_READFUNC_ABORT);
-c_static_assert(PAL_CURL_READFUNC_PAUSE == CURL_READFUNC_PAUSE);
-c_static_assert(PAL_CURL_WRITEFUNC_PAUSE == CURL_WRITEFUNC_PAUSE);
-
-c_static_assert(PAL_CURLINFO_TEXT == CURLINFO_TEXT);
-c_static_assert(PAL_CURLINFO_HEADER_IN == CURLINFO_HEADER_IN);
-c_static_assert(PAL_CURLINFO_HEADER_OUT == CURLINFO_HEADER_OUT);
-c_static_assert(PAL_CURLINFO_DATA_IN == CURLINFO_DATA_IN);
-c_static_assert(PAL_CURLINFO_DATA_OUT == CURLINFO_DATA_OUT);
-c_static_assert(PAL_CURLINFO_SSL_DATA_IN == CURLINFO_SSL_DATA_IN);
-c_static_assert(PAL_CURLINFO_SSL_DATA_OUT == CURLINFO_SSL_DATA_OUT);
-
-c_static_assert(PAL_CURL_MAX_HTTP_HEADER == CURL_MAX_HTTP_HEADER);
-
-CURL* HttpNative_EasyCreate()
-{
- return curl_easy_init();
-}
-
-void HttpNative_EasyDestroy(CURL* handle)
-{
- curl_easy_cleanup(handle);
-}
-
-inline static CURLoption ConvertOption(PAL_CURLoption option)
-{
- return (CURLoption)option;
-}
-
-int32_t HttpNative_EasySetOptionString(CURL* handle, PAL_CURLoption option, const char* value)
-{
- return (int32_t)(curl_easy_setopt(handle, ConvertOption(option), value));
-}
-
-int32_t HttpNative_EasySetOptionLong(CURL* handle, PAL_CURLoption option, int64_t value)
-{
- CURLoption curlOpt = ConvertOption(option);
-
- // The HttpNative_EasySetOptionLong entrypoint is used for both curl_easy_setopt(..., long) and
- // curl_easy_setopt(..., curl_off_t). As they'll likely be different sizes on 32-bit platforms,
- // we map anything >= CurlOptionOffTBase to use curl_off_t.
- if (option >= CurlOptionOffTBase)
- {
- return (int32_t)(curl_easy_setopt(handle, curlOpt, (curl_off_t)value));
- }
- else
- {
- return (int32_t)(curl_easy_setopt(handle, curlOpt, (long)value));
- }
-}
-
-int32_t HttpNative_EasySetOptionPointer(CURL* handle, PAL_CURLoption option, void* value)
-{
- return (int32_t)(curl_easy_setopt(handle, ConvertOption(option), value));
-}
-
-const char* HttpNative_EasyGetErrorString(PAL_CURLcode code)
-{
- return curl_easy_strerror((CURLcode)code);
-}
-
-inline static CURLINFO ConvertInfo(PAL_CURLINFO info)
-{
- return (CURLINFO)info;
-}
-
-int32_t HttpNative_EasyGetInfoPointer(CURL* handle, PAL_CURLINFO info, void** value)
-{
- return (int32_t)(curl_easy_getinfo(handle, ConvertInfo(info), value));
-}
-
-int32_t HttpNative_EasyGetInfoLong(CURL* handle, PAL_CURLINFO info, int64_t* value)
-{
- return (int32_t)(curl_easy_getinfo(handle, ConvertInfo(info), value));
-}
-
-int32_t HttpNative_EasyPerform(CURL* handle)
-{
- return (int32_t)(curl_easy_perform(handle));
-}
-
-int32_t HttpNative_EasyUnpause(CURL* handle)
-{
- return (int32_t)(curl_easy_pause(handle, CURLPAUSE_CONT));
-}
-
-struct CallbackHandle
-{
- SeekCallback seekCallback;
- void* seekUserPointer;
-
- ReadWriteCallback writeCallback;
- void* writeUserPointer;
-
- ReadWriteCallback readCallback;
- void* readUserPointer;
-
- ReadWriteCallback headerCallback;
- void* headerUserPointer;
-
- SslCtxCallback sslCtxCallback;
- void* sslUserPointer;
-
- DebugCallback debugCallback;
- void* debugUserPointer;
-};
-
-static inline bool EnsureCallbackHandle(CallbackHandle** callbackHandle)
-{
- assert(callbackHandle != NULL);
-
- if (*callbackHandle == NULL)
- {
- *callbackHandle = (CallbackHandle*)malloc(sizeof(CallbackHandle));
- }
-
- return *callbackHandle != NULL;
-}
-
-static int seek_callback(void* userp, curl_off_t offset, int origin)
-{
- CallbackHandle* handle = (CallbackHandle*)userp;
- return handle->seekCallback(handle->seekUserPointer, offset, origin);
-}
-
-void
-HttpNative_RegisterSeekCallback(CURL* curl, SeekCallback callback, void* userPointer, CallbackHandle** callbackHandle)
-{
- if (EnsureCallbackHandle(callbackHandle))
- {
- CallbackHandle* handle = *callbackHandle;
- handle->seekCallback = callback;
- handle->seekUserPointer = userPointer;
-
- curl_easy_setopt(curl, CURLOPT_SEEKDATA, handle);
- curl_easy_setopt(curl, CURLOPT_SEEKFUNCTION, &seek_callback);
- }
-}
-
-static size_t write_callback(char* buffer, size_t size, size_t nitems, void* instream)
-{
- CallbackHandle* handle = (CallbackHandle*)instream;
- return (size_t)(handle->writeCallback((uint8_t*)buffer, size, nitems, handle->writeUserPointer));
-}
-
-static size_t read_callback(char* buffer, size_t size, size_t nitems, void* instream)
-{
- CallbackHandle* handle = (CallbackHandle*)instream;
- return (size_t)(handle->readCallback((uint8_t*)buffer, size, nitems, handle->readUserPointer));
-}
-
-static size_t header_callback(char* buffer, size_t size, size_t nitems, void* instream)
-{
- CallbackHandle* handle = (CallbackHandle*)instream;
- return (size_t)(handle->headerCallback((uint8_t*)buffer, size, nitems, handle->headerUserPointer));
-}
-
-void HttpNative_RegisterReadWriteCallback(CURL* curl,
- ReadWriteFunction functionType,
- ReadWriteCallback callback,
- void* userPointer,
- CallbackHandle** callbackHandle)
-{
- if (EnsureCallbackHandle(callbackHandle))
- {
- CallbackHandle* handle = *callbackHandle;
-
- switch (functionType)
- {
- case Write:
- handle->writeCallback = callback;
- handle->writeUserPointer = userPointer;
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, handle);
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &write_callback);
- break;
-
- case Read:
- handle->readCallback = callback;
- handle->readUserPointer = userPointer;
- curl_easy_setopt(curl, CURLOPT_READDATA, handle);
- curl_easy_setopt(curl, CURLOPT_READFUNCTION, &read_callback);
- break;
-
- case Header:
- handle->headerCallback = callback;
- handle->headerUserPointer = userPointer;
- curl_easy_setopt(curl, CURLOPT_HEADERDATA, handle);
- curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, &header_callback);
- break;
- }
- }
-}
-
-static CURLcode ssl_ctx_callback(CURL* curl, void* sslCtx, void* userPointer)
-{
- CallbackHandle* handle = (CallbackHandle*)userPointer;
-
- int32_t result = handle->sslCtxCallback(curl, sslCtx, handle->sslUserPointer);
- return (CURLcode)result;
-}
-
-int32_t HttpNative_RegisterSslCtxCallback(CURL* curl,
- SslCtxCallback callback,
- void* userPointer,
- CallbackHandle** callbackHandle)
-{
- if (!EnsureCallbackHandle(callbackHandle))
- {
- return CURLE_OUT_OF_MEMORY;
- }
-
- CallbackHandle* handle = *callbackHandle;
- handle->sslCtxCallback = callback;
- handle->sslUserPointer = userPointer;
-
- curl_easy_setopt(curl, CURLOPT_SSL_CTX_DATA, handle);
- return (int32_t)(curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, &ssl_ctx_callback));
-}
-
-static int debug_callback(CURL* curl, curl_infotype type, char* data, size_t size, void* userPointer)
-{
- assert(userPointer != NULL);
- CallbackHandle* handle = (CallbackHandle*)userPointer;
- handle->debugCallback(curl, (PAL_CurlInfoType)type, data, size, handle->debugUserPointer);
- return 0;
-}
-
-int32_t HttpNative_RegisterDebugCallback(CURL* curl,
- DebugCallback callback,
- void* userPointer,
- CallbackHandle** callbackHandle)
-{
- if (!EnsureCallbackHandle(callbackHandle))
- {
- return CURLE_OUT_OF_MEMORY;
- }
-
- CallbackHandle* handle = *callbackHandle;
- handle->debugCallback = callback;
- handle->debugUserPointer = userPointer;
-
- CURLcode rv = curl_easy_setopt(curl, CURLOPT_DEBUGDATA, handle);
- return rv == CURLE_OK ?
- (int32_t)(curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, &debug_callback)) :
- (int32_t)rv;
-}
-
-void HttpNative_FreeCallbackHandle(CallbackHandle* callbackHandle)
-{
- assert(callbackHandle != NULL);
- if (callbackHandle != NULL)
- {
- free(callbackHandle);
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-#pragma once
-
-#include "pal_types.h"
-#include "pal_compiler.h"
-
-#include <curl/curl.h>
-
-enum
-{
- CurlOptionLongBase = 0,
- CurlOptionObjectPointBase = 10000,
- CurlOptionOffTBase = 30000,
-};
-
-typedef enum
-{
- PAL_CURLOPT_INFILESIZE = CurlOptionLongBase + 14,
- PAL_CURLOPT_SSLVERSION = CurlOptionLongBase + 32,
- PAL_CURLOPT_VERBOSE = CurlOptionLongBase + 41,
- PAL_CURLOPT_NOBODY = CurlOptionLongBase + 44,
- PAL_CURLOPT_UPLOAD = CurlOptionLongBase + 46,
- PAL_CURLOPT_POST = CurlOptionLongBase + 47,
- PAL_CURLOPT_FOLLOWLOCATION = CurlOptionLongBase + 52,
- PAL_CURLOPT_PROXYPORT = CurlOptionLongBase + 59,
- PAL_CURLOPT_POSTFIELDSIZE = CurlOptionLongBase + 60,
- PAL_CURLOPT_SSL_VERIFYPEER = CurlOptionLongBase + 64,
- PAL_CURLOPT_MAXREDIRS = CurlOptionLongBase + 68,
- PAL_CURLOPT_SSL_VERIFYHOST = CurlOptionLongBase + 81,
- PAL_CURLOPT_HTTP_VERSION = CurlOptionLongBase + 84,
- PAL_CURLOPT_DNS_CACHE_TIMEOUT = CurlOptionLongBase + 92,
- PAL_CURLOPT_NOSIGNAL = CurlOptionLongBase + 99,
- PAL_CURLOPT_PROXYTYPE = CurlOptionLongBase + 101,
- PAL_CURLOPT_HTTPAUTH = CurlOptionLongBase + 107,
- PAL_CURLOPT_TCP_NODELAY = CurlOptionLongBase + 121,
- PAL_CURLOPT_TCP_KEEPALIVE = CurlOptionLongBase + 213,
- PAL_CURLOPT_CONNECTTIMEOUT_MS = CurlOptionLongBase + 156,
- PAL_CURLOPT_ADDRESS_SCOPE = CurlOptionLongBase + 171,
- PAL_CURLOPT_PROTOCOLS = CurlOptionLongBase + 181,
- PAL_CURLOPT_REDIR_PROTOCOLS = CurlOptionLongBase + 182,
-
- PAL_CURLOPT_URL = CurlOptionObjectPointBase + 2,
- PAL_CURLOPT_PROXY = CurlOptionObjectPointBase + 4,
- PAL_CURLOPT_PROXYUSERPWD = CurlOptionObjectPointBase + 6,
- PAL_CURLOPT_COOKIE = CurlOptionObjectPointBase + 22,
- PAL_CURLOPT_HTTPHEADER = CurlOptionObjectPointBase + 23,
- PAL_CURLOPT_CUSTOMREQUEST = CurlOptionObjectPointBase + 36,
- PAL_CURLOPT_ACCEPT_ENCODING = CurlOptionObjectPointBase + 102,
- PAL_CURLOPT_PRIVATE = CurlOptionObjectPointBase + 103,
- PAL_CURLOPT_COPYPOSTFIELDS = CurlOptionObjectPointBase + 165,
- PAL_CURLOPT_USERNAME = CurlOptionObjectPointBase + 173,
- PAL_CURLOPT_PASSWORD = CurlOptionObjectPointBase + 174,
- PAL_CURLOPT_CAPATH = CurlOptionObjectPointBase + 97,
- PAL_CURLOPT_PROXY_CAPATH = CurlOptionObjectPointBase + 247,
- PAL_CURLOPT_CAINFO = CurlOptionObjectPointBase + 65,
- PAL_CURLOPT_PROXY_CAINFO = CurlOptionObjectPointBase + 246,
-
- PAL_CURLOPT_INFILESIZE_LARGE = CurlOptionOffTBase + 115,
- PAL_CURLOPT_POSTFIELDSIZE_LARGE = CurlOptionOffTBase + 120,
-} PAL_CURLoption;
-
-typedef enum
-{
- Write = 0,
- Read = 1,
- Header = 2,
-} ReadWriteFunction;
-
-typedef enum
-{
- PAL_CURLE_OK = 0,
- PAL_CURLE_UNSUPPORTED_PROTOCOL = 1,
- PAL_CURLE_FAILED_INIT = 2,
- PAL_CURLE_NOT_BUILT_IN = 4,
- PAL_CURLE_COULDNT_RESOLVE_HOST = 6,
- PAL_CURLE_OUT_OF_MEMORY = 27,
- PAL_CURLE_OPERATION_TIMEDOUT = 28,
- PAL_CURLE_ABORTED_BY_CALLBACK = 42,
- PAL_CURLE_UNKNOWN_OPTION = 48,
- PAL_CURLE_RECV_ERROR = 56,
- PAL_CURLE_SEND_FAIL_REWIND = 65,
-} PAL_CURLcode;
-
-enum
-{
- CurlInfoStringBase = 0x100000,
- CurlInfoLongBase = 0x200000,
-};
-
-typedef enum
-{
- PAL_CURL_HTTP_VERSION_NONE = 0,
- PAL_CURL_HTTP_VERSION_1_0 = 1,
- PAL_CURL_HTTP_VERSION_1_1 = 2,
- PAL_CURL_HTTP_VERSION_2TLS = 4
-} PAL_CURL_HTTP_VERSION;
-
-typedef enum
-{
- PAL_CURL_SSLVERSION_TLSv1 = 1,
- PAL_CURL_SSLVERSION_SSLv2 = 2,
- PAL_CURL_SSLVERSION_SSLv3 = 3,
- PAL_CURL_SSLVERSION_TLSv1_0 = 4,
- PAL_CURL_SSLVERSION_TLSv1_1 = 5,
- PAL_CURL_SSLVERSION_TLSv1_2 = 6,
-} PAL_CURL_SSLVERSION;
-
-typedef enum
-{
- PAL_CURLINFO_EFFECTIVE_URL = CurlInfoStringBase + 1,
- PAL_CURLINFO_PRIVATE = CurlInfoStringBase + 21,
- PAL_CURLINFO_HTTPAUTH_AVAIL = CurlInfoLongBase + 23,
-} PAL_CURLINFO;
-
-typedef enum
-{
- PAL_CURLAUTH_None = 0,
- PAL_CURLAUTH_Basic = 1 << 0,
- PAL_CURLAUTH_Digest = 1 << 1,
- PAL_CURLAUTH_Negotiate = 1 << 2,
- PAL_CURLAUTH_NTLM = 1 << 3,
-} PAL_CURLAUTH;
-
-typedef enum
-{
- PAL_CURLPROXY_HTTP = 0,
-} PAL_CURLPROXYTYPE;
-
-typedef enum
-{
- PAL_CURLPROTO_HTTP = (1 << 0),
- PAL_CURLPROTO_HTTPS = (1 << 1),
-} PAL_CURLPROTO;
-
-typedef enum
-{
- PAL_CURL_SEEKFUNC_OK = 0,
- PAL_CURL_SEEKFUNC_FAIL = 1,
- PAL_CURL_SEEKFUNC_CANTSEEK = 2,
-} PAL_CurlSeekResult;
-
-typedef enum
-{
- PAL_CURLINFO_TEXT = 0,
- PAL_CURLINFO_HEADER_IN = 1,
- PAL_CURLINFO_HEADER_OUT = 2,
- PAL_CURLINFO_DATA_IN = 3,
- PAL_CURLINFO_DATA_OUT = 4,
- PAL_CURLINFO_SSL_DATA_IN = 5,
- PAL_CURLINFO_SSL_DATA_OUT = 6,
-} PAL_CurlInfoType;
-
-enum {
- PAL_CURL_READFUNC_ABORT = 0x10000000,
- PAL_CURL_READFUNC_PAUSE = 0x10000001,
- PAL_CURL_WRITEFUNC_PAUSE = 0x10000001,
- PAL_CURL_MAX_HTTP_HEADER = 100 * 1024
-};
-
-/*
-Creates a new CURL instance.
-
-Returns the new CURL instance or nullptr if something went wrong.
-*/
-DLLEXPORT CURL* HttpNative_EasyCreate(void);
-
-/*
-Cleans up and deletes a CURL instance.
-
-No-op if handle is null.
-The given CURL pointer is invalid after this call.
-*/
-DLLEXPORT void HttpNative_EasyDestroy(CURL* handle);
-
-/*
-Shims the curl_easy_setopt function, which takes a variable number of
-arguments, but must be a long, a function pointer, an object pointer or a curl_off_t,
-depending on what option is supplied.
-*/
-DLLEXPORT int32_t HttpNative_EasySetOptionString(CURL* handle, PAL_CURLoption option, const char* value);
-DLLEXPORT int32_t HttpNative_EasySetOptionLong(CURL* handle, PAL_CURLoption option, int64_t value);
-DLLEXPORT int32_t HttpNative_EasySetOptionPointer(CURL* handle, PAL_CURLoption option, void* value);
-
-/*
-Returns a string describing the CURLcode error code.
-*/
-DLLEXPORT const char* HttpNative_EasyGetErrorString(PAL_CURLcode code);
-
-/*
-Shims the curl_easy_setopt function, which takes a variable number of
-arguments.
-*/
-DLLEXPORT int32_t HttpNative_EasyGetInfoPointer(CURL* handle, PAL_CURLINFO info, void** value);
-DLLEXPORT int32_t HttpNative_EasyGetInfoLong(CURL* handle, PAL_CURLINFO info, int64_t* value);
-
-/*
-Shims the curl_easy_perform function.
-
-Returns CURLE_OK (0) if everything was ok, non-zero means an error occurred.
-*/
-DLLEXPORT int32_t HttpNative_EasyPerform(CURL* handle);
-
-/*
-Unpauses the CURL request.
-
-Returns CURLE_OK (0) if everything was ok, non-zero means an error occurred.
-*/
-DLLEXPORT int32_t HttpNative_EasyUnpause(CURL* handle);
-
-// the function pointer definition for the callback used in RegisterSeekCallback
-typedef int32_t (*SeekCallback)(void* userPointer, int64_t offset, int32_t origin);
-
-// the function pointer definition for the callback used in RegisterReadWriteCallback
-typedef uint64_t (*ReadWriteCallback)(uint8_t* buffer, uint64_t bufferSize, uint64_t nitems, void* userPointer);
-
-// the function pointer definition for the callback used in RegisterSslCtxCallback
-typedef int32_t (*SslCtxCallback)(CURL* curl, void* sslCtx, void* userPointer);
-
-// the function pointer definition for the callback used for debugging callbacks
-typedef void(*DebugCallback)(CURL* curl, PAL_CurlInfoType type, char* data, uint64_t size, void* userPointer);
-
-/*
-The object that is returned from RegisterXXXCallback functions.
-This holds the data necessary to know what managed callback to invoke and with what args.
-*/
-typedef struct CallbackHandle CallbackHandle;
-
-/*
-Registers a callback in libcurl for seeking in an input stream.
-
-This function gets called by libcurl to seek to a certain position in the input stream
-and can be used to fast forward a file in a resumed upload.
-*/
-DLLEXPORT void
-HttpNative_RegisterSeekCallback(CURL* curl, SeekCallback callback, void* userPointer, CallbackHandle** callbackHandle);
-
-/*
-Registers a callback in libcurl for reading/writing input/output streams.
-*/
-DLLEXPORT void HttpNative_RegisterReadWriteCallback(CURL* curl,
- ReadWriteFunction functionType,
- ReadWriteCallback callback,
- void* userPointer,
- CallbackHandle** callbackHandle);
-
-/*
-Registers a callback in libcurl for initializing SSL connections.
-
-This callback function gets called by libcurl just before the initialization of an SSL connection
-after having processed all other SSL related options to give a last chance to an application
-to modify the behaviour of the SSL initialization.
-
-Returns a CURLcode that describes whether registering the callback was successful or not.
-*/
-DLLEXPORT int32_t HttpNative_RegisterSslCtxCallback(CURL* curl,
- SslCtxCallback callback,
- void* userPointer,
- CallbackHandle** callbackHandle);
-
-/*
-Registers a callback in libcurl for outputting debug information.
-
-This callback function gets called by libcurl each time it has debug information to report.
-
-Returns a CURLcode that describes whether registering the callback was successful or not.
-*/
-DLLEXPORT int32_t HttpNative_RegisterDebugCallback(CURL* curl,
- DebugCallback callback,
- void* userPointer,
- CallbackHandle** callbackHandle);
-
-/*
-Frees the CallbackHandle created by a RegisterXXXCallback function.
-*/
-DLLEXPORT void HttpNative_FreeCallbackHandle(CallbackHandle* callbackHandle);
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-#include "pal_config.h"
-#include "pal_multi.h"
-#include "pal_utilities.h"
-
-#include <assert.h>
-#include <poll.h>
-
-c_static_assert(PAL_CURLM_CALL_MULTI_PERFORM == CURLM_CALL_MULTI_PERFORM);
-c_static_assert(PAL_CURLM_OK == CURLM_OK);
-c_static_assert(PAL_CURLM_BAD_HANDLE == CURLM_BAD_HANDLE);
-c_static_assert(PAL_CURLM_BAD_EASY_HANDLE == CURLM_BAD_EASY_HANDLE);
-c_static_assert(PAL_CURLM_OUT_OF_MEMORY == CURLM_OUT_OF_MEMORY);
-c_static_assert(PAL_CURLM_INTERNAL_ERROR == CURLM_INTERNAL_ERROR);
-c_static_assert(PAL_CURLM_BAD_SOCKET == CURLM_BAD_SOCKET);
-c_static_assert(PAL_CURLM_UNKNOWN_OPTION == CURLM_UNKNOWN_OPTION);
-#if HAVE_CURLM_ADDED_ALREADY
-c_static_assert(PAL_CURLM_ADDED_ALREADY == CURLM_ADDED_ALREADY);
-#endif
-c_static_assert(PAL_CURLMOPT_PIPELINING == CURLMOPT_PIPELINING);
-#ifdef CURLMOPT_MAX_HOST_CONNECTIONS
-c_static_assert(PAL_CURLMOPT_MAX_HOST_CONNECTIONS == CURLMOPT_MAX_HOST_CONNECTIONS);
-#endif
-#if HAVE_CURLPIPE_MULTIPLEX
-c_static_assert(PAL_CURLPIPE_MULTIPLEX == CURLPIPE_MULTIPLEX);
-#endif
-
-c_static_assert(PAL_CURLMSG_DONE == CURLMSG_DONE);
-
-CURLM* HttpNative_MultiCreate()
-{
- return curl_multi_init();
-}
-
-int32_t HttpNative_MultiDestroy(CURLM* multiHandle)
-{
- return curl_multi_cleanup(multiHandle);
-}
-
-int32_t HttpNative_MultiAddHandle(CURLM* multiHandle, CURL* easyHandle)
-{
- return curl_multi_add_handle(multiHandle, easyHandle);
-}
-
-int32_t HttpNative_MultiRemoveHandle(CURLM* multiHandle, CURL* easyHandle)
-{
- return curl_multi_remove_handle(multiHandle, easyHandle);
-}
-
-int32_t HttpNative_MultiWait(CURLM* multiHandle,
- intptr_t extraFileDescriptor,
- int32_t* isExtraFileDescriptorActive,
- int32_t* isTimeout)
-{
- assert(isExtraFileDescriptorActive != NULL);
- assert(isTimeout != NULL);
-
- struct curl_waitfd extraFds = {.fd = ToFileDescriptor(extraFileDescriptor), .events = CURL_WAIT_POLLIN, .revents = 0};
-
- // Even with our cancellation mechanism, we specify a timeout so that
- // just in case something goes wrong we can recover gracefully. This timeout is relatively long.
- // Note, though, that libcurl has its own internal timeout, which can be requested separately
- // via curl_multi_timeout, but which is used implicitly by curl_multi_wait if it's shorter
- // than the value we provide.
- const int FailsafeTimeoutMilliseconds = 1000;
-
- int numFds;
- CURLMcode result = curl_multi_wait(multiHandle, &extraFds, 1, FailsafeTimeoutMilliseconds, &numFds);
-
- if (numFds == 0)
- {
- *isTimeout = true;
- *isExtraFileDescriptorActive = false;
- }
- else
- {
- *isTimeout = false;
-
- //
- // Prior to libcurl version 7.32.0, the revents field was not returned properly for "extra" file descriptors
- // passed to curl_multi_wait. See https://github.com/dotnet/corefx/issues/9751. So if we have a libcurl
- // prior to that version, we need to do our own poll to get the status of the extra file descriptor.
- //
- if (curl_version_info(CURLVERSION_NOW)->version_num >= 0x072000)
- {
- *isExtraFileDescriptorActive = (extraFds.revents & CURL_WAIT_POLLIN) != 0;
- }
- else
- {
- struct pollfd pfd = { .fd = ToFileDescriptor(extraFileDescriptor),.events = POLLIN,.revents = 0 };
- poll(&pfd, 1, 0);
-
- //
- // We ignore any failure in poll(), to preserve the result from curl_multi_wait. If poll() fails, it should
- // leave revents cleared.
- //
- *isExtraFileDescriptorActive = (pfd.revents & POLLIN) != 0;
- }
- }
-
- return result;
-}
-
-int32_t HttpNative_MultiPerform(CURLM* multiHandle)
-{
- int running_handles;
- return curl_multi_perform(multiHandle, &running_handles);
-}
-
-int32_t HttpNative_MultiInfoRead(CURLM* multiHandle, int32_t* message, CURL** easyHandle, int32_t* result)
-{
- assert(message != NULL);
- assert(easyHandle != NULL);
- assert(result != NULL);
-
- int msgs_in_queue;
- CURLMsg* curlMessage = curl_multi_info_read(multiHandle, &msgs_in_queue);
- if (curlMessage == NULL)
- {
- *message = 0;
- *easyHandle = NULL;
- *result = 0;
-
- return 0;
- }
-
- *message = (int32_t)(curlMessage->msg);
- *easyHandle = curlMessage->easy_handle;
- *result = (int32_t)(curlMessage->data.result);
-
- return 1;
-}
-
-const char* HttpNative_MultiGetErrorString(PAL_CURLMcode code)
-{
- return curl_multi_strerror((CURLMcode)code);
-}
-
-int32_t HttpNative_MultiSetOptionLong(CURLM* handle, PAL_CURLMoption option, int64_t value)
-{
- return curl_multi_setopt(handle, (CURLMoption)option, value);
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-#pragma once
-
-#include "pal_types.h"
-#include "pal_compiler.h"
-
-#include <curl/curl.h>
-
-typedef enum
-{
- PAL_CURLM_CALL_MULTI_PERFORM = -1,
- PAL_CURLM_OK = 0,
- PAL_CURLM_BAD_HANDLE = 1,
- PAL_CURLM_BAD_EASY_HANDLE = 2,
- PAL_CURLM_OUT_OF_MEMORY = 3,
- PAL_CURLM_INTERNAL_ERROR = 4,
- PAL_CURLM_BAD_SOCKET = 5,
- PAL_CURLM_UNKNOWN_OPTION = 6,
- PAL_CURLM_ADDED_ALREADY = 7, // Added in libcurl 7.32.1
-} PAL_CURLMcode;
-
-typedef enum
-{
- PAL_CURLMOPT_PIPELINING = 3,
- PAL_CURLMOPT_MAX_HOST_CONNECTIONS = 7,
-} PAL_CURLMoption;
-
-typedef enum
-{
- PAL_CURLPIPE_MULTIPLEX = 2,
-} PAL_CurlPipe;
-
-typedef enum
-{
- PAL_CURLMSG_DONE = 1,
-} PAL_CURLMSG;
-
-/*
-Creates a new CURLM instance.
-
-Returns the new CURLM instance or nullptr if something went wrong.
-*/
-DLLEXPORT CURLM* HttpNative_MultiCreate(void);
-
-/*
-Cleans up and removes a whole multi stack.
-
-Returns CURLM_OK on success, otherwise an error code.
-*/
-DLLEXPORT int32_t HttpNative_MultiDestroy(CURLM* multiHandle);
-
-/*
-Shims the curl_multi_add_handle function.
-
-Returns CURLM_OK on success, otherwise an error code.
-*/
-DLLEXPORT int32_t HttpNative_MultiAddHandle(CURLM* multiHandle, CURL* easyHandle);
-
-/*
-Shims the curl_multi_remove_handle function.
-
-Returns CURLM_OK on success, otherwise an error code.
-*/
-DLLEXPORT int32_t HttpNative_MultiRemoveHandle(CURLM* multiHandle, CURL* easyHandle);
-
-/*
-Shims the curl_multi_wait function.
-
-Returns CURLM_OK on success, otherwise an error code.
-
-isExtraFileDescriptorActive is set to a value indicating whether extraFileDescriptor has new data received.
-isTimeout is set to a value indicating whether a timeout was encountered before any file descriptors had events occur.
-*/
-DLLEXPORT int32_t HttpNative_MultiWait(CURLM* multiHandle,
- intptr_t extraFileDescriptor,
- int32_t* isExtraFileDescriptorActive,
- int32_t* isTimeout);
-
-/*
-Reads/writes available data from each easy handle.
-Shims the curl_multi_perform function.
-
-Returns CURLM_OK on success, otherwise an error code.
-*/
-DLLEXPORT int32_t HttpNative_MultiPerform(CURLM* multiHandle);
-
-/*
-Ask the multi handle if there are any messages/informationals from the individual transfers.
-Shims the curl_multi_info_read function.
-
-Returns 1 if a CURLMsg was retrieved and the out variables are set,
-otherwise 0 when there are no more messages to retrieve.
-*/
-DLLEXPORT int32_t HttpNative_MultiInfoRead(CURLM* multiHandle, int32_t* message, CURL** easyHandle, int32_t* result);
-
-/*
-Returns a string describing the CURLMcode error code.
-*/
-DLLEXPORT const char* HttpNative_MultiGetErrorString(PAL_CURLMcode code);
-
-/*
-Shims the curl_multi_setopt function
-*/
-DLLEXPORT int32_t HttpNative_MultiSetOptionLong(CURLM* handle, PAL_CURLMoption option, int64_t value);
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-#include "pal_slist.h"
-
-struct curl_slist* HttpNative_SListAppend(struct curl_slist* list, const char* headerValue)
-{
- return curl_slist_append(list, headerValue);
-}
-
-void HttpNative_SListFreeAll(struct curl_slist* list)
-{
- curl_slist_free_all(list);
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-#pragma once
-
-#include "pal_types.h"
-#include "pal_compiler.h"
-
-#include <curl/curl.h>
-
-/*
-Appends a specified string to a linked list of strings.
-
-Returns a null pointer if anything went wrong, otherwise the new list pointer.
-*/
-DLLEXPORT struct curl_slist* HttpNative_SListAppend(struct curl_slist* list, const char* headerValue);
-
-/*
-Removes all traces of a previously built curl_slist linked list.
-*/
-DLLEXPORT void HttpNative_SListFreeAll(struct curl_slist* list);
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-#include "pal_config.h"
-#include "pal_versioninfo.h"
-
-#include <string.h>
-#include <curl/curl.h>
-
-c_static_assert(PAL_CURL_VERSION_IPV6 == CURL_VERSION_IPV6);
-c_static_assert(PAL_CURL_VERSION_KERBEROS4 == CURL_VERSION_KERBEROS4);
-c_static_assert(PAL_CURL_VERSION_SSL == CURL_VERSION_SSL);
-c_static_assert(PAL_CURL_VERSION_LIBZ == CURL_VERSION_LIBZ);
-c_static_assert(PAL_CURL_VERSION_NTLM == CURL_VERSION_NTLM);
-c_static_assert(PAL_CURL_VERSION_GSSNEGOTIATE == CURL_VERSION_GSSNEGOTIATE);
-c_static_assert(PAL_CURL_VERSION_DEBUG == CURL_VERSION_DEBUG);
-c_static_assert(PAL_CURL_VERSION_ASYNCHDNS == CURL_VERSION_ASYNCHDNS);
-c_static_assert(PAL_CURL_VERSION_SPNEGO == CURL_VERSION_SPNEGO);
-c_static_assert(PAL_CURL_VERSION_LARGEFILE == CURL_VERSION_LARGEFILE);
-c_static_assert(PAL_CURL_VERSION_IDN == CURL_VERSION_IDN);
-c_static_assert(PAL_CURL_VERSION_SSPI == CURL_VERSION_SSPI);
-c_static_assert(PAL_CURL_VERSION_CONV == CURL_VERSION_CONV);
-c_static_assert(PAL_CURL_VERSION_CURLDEBUG == CURL_VERSION_CURLDEBUG);
-c_static_assert(PAL_CURL_VERSION_TLSAUTH_SRP == CURL_VERSION_TLSAUTH_SRP);
-c_static_assert(PAL_CURL_VERSION_NTLM_WB == CURL_VERSION_NTLM_WB);
-#ifdef CURL_VERSION_HTTP2
-c_static_assert(PAL_CURL_VERSION_HTTP2 == CURL_VERSION_HTTP2);
-#endif
-#ifdef CURL_VERSION_GSSAPI
-c_static_assert(PAL_CURL_VERSION_GSSAPI == CURL_VERSION_GSSAPI);
-#endif
-#ifdef CURL_VERSION_KERBEROS5
-c_static_assert(PAL_CURL_VERSION_KERBEROS5 == CURL_VERSION_KERBEROS5);
-#endif
-#ifdef CURL_VERSION_UNIX_SOCKETS
-c_static_assert(PAL_CURL_VERSION_UNIX_SOCKETS == CURL_VERSION_UNIX_SOCKETS);
-#endif
-#ifdef CURL_VERSION_PSL
-c_static_assert(PAL_CURL_VERSION_PSL == CURL_VERSION_PSL);
-#endif
-
-// Based on docs/libcurl/symbols-in-versions in libcurl source tree,
-// the CURL_VERSION_HTTP2 was introduced in libcurl 7.33.0 and
-// the CURLPIPE_MULTIPLEX was introduced in libcurl 7.43.0.
-#define MIN_VERSION_WITH_CURLPIPE_MULTIPLEX 0x072B00
-
-int32_t HttpNative_GetSupportedFeatures()
-{
- curl_version_info_data* info = curl_version_info(CURLVERSION_NOW);
- return info != NULL ? info->features : 0;
-}
-
-int32_t HttpNative_GetSupportsHttp2Multiplexing()
-{
- curl_version_info_data* info = curl_version_info(CURLVERSION_NOW);
- return info != NULL &&
- (info->version_num >= MIN_VERSION_WITH_CURLPIPE_MULTIPLEX) &&
- ((info->features & PAL_CURL_VERSION_HTTP2) == PAL_CURL_VERSION_HTTP2) ? 1 : 0;
-}
-
-char* HttpNative_GetVersionDescription()
-{
- curl_version_info_data* info = curl_version_info(CURLVERSION_NOW);
- return info != NULL && info->version != NULL ? strdup(info->version) : NULL;
-}
-
-char* HttpNative_GetSslVersionDescription()
-{
- curl_version_info_data* info = curl_version_info(CURLVERSION_NOW);
- return info != NULL && info->ssl_version != NULL ? strdup(info->ssl_version) : NULL;
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-#pragma once
-#include "pal_types.h"
-#include "pal_compiler.h"
-
-/**
-* Constants from curl.h for supported features
-*/
-typedef enum
-{
- PAL_CURL_VERSION_IPV6 = (1<<0),
- PAL_CURL_VERSION_KERBEROS4 = (1<<1),
- PAL_CURL_VERSION_SSL = (1<<2),
- PAL_CURL_VERSION_LIBZ = (1<<3),
- PAL_CURL_VERSION_NTLM = (1<<4),
- PAL_CURL_VERSION_GSSNEGOTIATE = (1<<5),
- PAL_CURL_VERSION_DEBUG = (1<<6),
- PAL_CURL_VERSION_ASYNCHDNS = (1<<7),
- PAL_CURL_VERSION_SPNEGO = (1<<8),
- PAL_CURL_VERSION_LARGEFILE = (1<<9),
- PAL_CURL_VERSION_IDN = (1<<10),
- PAL_CURL_VERSION_SSPI = (1<<11),
- PAL_CURL_VERSION_CONV = (1<<12),
- PAL_CURL_VERSION_CURLDEBUG = (1<<13),
- PAL_CURL_VERSION_TLSAUTH_SRP = (1<<14),
- PAL_CURL_VERSION_NTLM_WB = (1<<15),
- PAL_CURL_VERSION_HTTP2 = (1<<16),
- PAL_CURL_VERSION_GSSAPI = (1<<17),
- PAL_CURL_VERSION_KERBEROS5 = (1<<18),
- PAL_CURL_VERSION_UNIX_SOCKETS = (1<<19),
- PAL_CURL_VERSION_PSL = (1<<20),
-} CurlFeatures;
-
-/*
-Gets the features supported by libcurl.
-
-Returns 1 if multiplexing is supported, otherwise 0.
-*/
-DLLEXPORT int32_t HttpNative_GetSupportedFeatures(void);
-
-/*
-Gets the features supported by libcurl.
-
-Returns 1 if multiplexing is supported, otherwise 0.
-*/
-DLLEXPORT int32_t HttpNative_GetSupportsHttp2Multiplexing(void);
-
-/*
-Gets a string description of the version in use.
-*/
-DLLEXPORT char* HttpNative_GetVersionDescription(void);
-
-/*
-Gets a string description of the SSL version in use.
-*/
-DLLEXPORT char* HttpNative_GetSslVersionDescription(void);
message(FATAL_ERROR "Cannot find inotify functions on a Linux platform.")
endif()
-check_c_source_compiles(
- "
- #include <curl/multi.h>
- int main(void) { int i = CURLM_ADDED_ALREADY; return 0; }
- "
- HAVE_CURLM_ADDED_ALREADY)
-
-check_c_source_compiles(
- "
- #include <curl/multi.h>
- int main(void) { int i = CURL_HTTP_VERSION_2TLS; return 0; }
- "
- HAVE_CURL_HTTP_VERSION_2TLS)
-
-check_c_source_compiles(
- "
- #include <curl/multi.h>
- int main(void) { int i = CURLPIPE_MULTIPLEX; return 0; }
- "
- HAVE_CURLPIPE_MULTIPLEX)
-
-check_c_source_compiles(
- "
- #include <curl/curl.h>
- int main(void)
- {
- int i = CURL_SSLVERSION_TLSv1_0;
- i = CURL_SSLVERSION_TLSv1_1;
- i = CURL_SSLVERSION_TLSv1_2;
- return 0;
- }
- "
- HAVE_CURL_SSLVERSION_TLSv1_012)
-
option(HeimdalGssApi "use heimdal implementation of GssApi" OFF)
if (HeimdalGssApi)
<Compile Include="System\Net\Http\HttpClientHandler.Core.cs" />
<Compile Include="System\Net\Http\HttpClientHandler.netcoreapp.cs" />
<Compile Include="System\Net\Http\HttpClientHandler.Unix.cs" />
- <Compile Include="System\Net\Http\CurlHandler\CurlHandler.cs" />
- <Compile Include="System\Net\Http\CurlHandler\CurlHandler.EasyRequest.cs" />
- <Compile Include="System\Net\Http\CurlHandler\CurlHandler.MultiAgent.cs" />
- <Compile Include="System\Net\Http\CurlHandler\CurlException.cs" />
- <Compile Include="System\Net\Http\CurlHandler\CurlHandler.CurlResponseMessage.cs" />
- <Compile Include="System\Net\Http\CurlHandler\CurlResponseHeaderReader.cs" />
<Compile Include="$(CommonPath)\System\StrongToWeakReference.cs">
<Link>Common\Interop\Unix\StrongToWeakReference.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\CoreLib\Interop\Unix\System.Native\Interop.Write.cs">
<Link>Common\Interop\Unix\libc\Interop.Write.cs</Link>
</Compile>
- <Compile Include="$(CommonPath)\Interop\Unix\System.Net.Http.Native\Interop.CURLcode.cs">
- <Link>Common\Interop\Unix\System.Net.Http.Native\Interop.CURLcode.cs</Link>
- </Compile>
- <Compile Include="$(CommonPath)\Interop\Unix\System.Net.Http.Native\Interop.Easy.cs">
- <Link>Common\Interop\Unix\System.Net.Http.Native\Interop.Easy.cs</Link>
- </Compile>
- <Compile Include="$(CommonPath)\Interop\Unix\System.Net.Http.Native\Interop.Initialization.cs">
- <Link>Common\Interop\Unix\System.Net.Http.Native\Interop.Initialization.cs</Link>
- </Compile>
- <Compile Include="$(CommonPath)\Interop\Unix\System.Net.Http.Native\Interop.Multi.cs">
- <Link>Common\Interop\Unix\System.Net.Http.Native\Interop.Multi.cs</Link>
- </Compile>
- <Compile Include="$(CommonPath)\Interop\Unix\System.Net.Http.Native\Interop.SList.cs">
- <Link>Common\Interop\Unix\System.Net.Http.Native\Interop.SList.cs</Link>
- </Compile>
- <Compile Include="$(CommonPath)\Interop\Unix\System.Net.Http.Native\Interop.VersionInfo.cs">
- <Link>Common\Interop\Unix\System.Net.Http.Native\Interop.VersionInfo.cs</Link>
- </Compile>
<Compile Include="$(CommonPath)\System\CharArrayHelpers.cs">
<Link>Common\System\CharArrayHelpers.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\System\Net\Security\CertificateValidation.Unix.cs">
<Link>Common\System\Net\Security\CertificateValidation.Unix.cs</Link>
</Compile>
- <Compile Include="System\Net\Http\CurlHandler\CurlHandler.ClientCertificateProvider.cs" />
- <Compile Include="System\Net\Http\CurlHandler\CurlHandler.SslProvider.Linux.cs" />
- </ItemGroup>
- <ItemGroup Condition=" '$(TargetsOSX)' == 'true' ">
- <Compile Include="System\Net\Http\CurlHandler\CurlHandler.SslProvider.OSX.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.Win32.Primitives" />
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Runtime.InteropServices;
-
-namespace System.Net.Http
-{
- internal sealed class CurlException : Exception
- {
- internal CurlException(int error, string message) : base(message)
- {
- HResult = error;
- }
-
- internal CurlException(int error, Exception innerException) : base(GetCurlErrorString(error, isMulti:false), innerException)
- {
- HResult = error;
- }
-
- internal CurlException(int error, bool isMulti) : this(error, GetCurlErrorString(error, isMulti))
- {
- }
-
- internal static string GetCurlErrorString(int code, bool isMulti)
- {
- IntPtr ptr = isMulti ? Interop.Http.MultiGetErrorString(code) : Interop.Http.EasyGetErrorString(code);
- return Marshal.PtrToStringAnsi(ptr);
- }
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-using System.Security.Cryptography.X509Certificates;
-using System.Security.Cryptography;
-using Microsoft.Win32.SafeHandles;
-
-namespace System.Net.Http
-{
- internal partial class CurlHandler : HttpMessageHandler
- {
- internal sealed class ClientCertificateProvider : IDisposable
- {
- internal GCHandle _gcHandle;
- internal readonly Interop.Ssl.ClientCertCallback _callback;
- private readonly X509Certificate2Collection _clientCertificates;
-
- internal ClientCertificateProvider(X509Certificate2Collection clientCertificates)
- {
- _gcHandle = GCHandle.Alloc(this);
- _callback = TlsClientCertCallback;
- _clientCertificates = clientCertificates;
- }
-
- private int TlsClientCertCallback(IntPtr ssl, out IntPtr certHandle, out IntPtr privateKeyHandle)
- {
- EventSourceTrace("SSL: {0}", ssl);
- const int CertificateSet = 1, NoCertificateSet = 0, SuspendHandshake = -1;
-
- certHandle = IntPtr.Zero;
- privateKeyHandle = IntPtr.Zero;
-
- if (ssl == IntPtr.Zero)
- {
- Debug.Fail("Expected valid SSL pointer");
- EventSourceTrace("Invalid SSL pointer in callback");
- return NoCertificateSet;
- }
-
- SafeSslHandle sslHandle = null;
- X509Chain chain = null;
- X509Certificate2 certificate = null;
- try
- {
- sslHandle = new SafeSslHandle(ssl, ownsHandle: false);
-
- ISet<string> issuerNames = GetRequestCertificateAuthorities(sslHandle);
-
- if (_clientCertificates != null) // manual mode
- {
- // If there's one certificate, just use it. Otherwise, try to find the best one.
- int certCount = _clientCertificates.Count;
- if (certCount == 1)
- {
- EventSourceTrace("Single certificate. Building chain.");
- certificate = _clientCertificates[0];
- chain = TLSCertificateExtensions.BuildNewChain(certificate, includeClientApplicationPolicy: false);
- }
- else
- {
- EventSourceTrace("Finding the best of {0} certificates", certCount);
- if (!_clientCertificates.TryFindClientCertificate(issuerNames, out certificate, out chain))
- {
- EventSourceTrace("No certificate set.");
- return NoCertificateSet;
- }
- }
- EventSourceTrace("Chain built.");
- }
- else if (!GetAutomaticClientCertificate(issuerNames, out certificate, out chain)) // automatic mode
- {
- EventSourceTrace("No automatic certificate or chain.");
- return NoCertificateSet;
- }
-
- SafeEvpPKeyHandle privateKeySafeHandle = null;
- Interop.Crypto.CheckValidOpenSslHandle(certificate.Handle);
- using (RSAOpenSsl rsa = certificate.GetRSAPrivateKey() as RSAOpenSsl)
- {
- if (rsa != null)
- {
- privateKeySafeHandle = rsa.DuplicateKeyHandle();
- EventSourceTrace("RSA key");
- }
- else
- {
- using (ECDsaOpenSsl ecdsa = certificate.GetECDsaPrivateKey() as ECDsaOpenSsl)
- {
- if (ecdsa != null)
- {
- privateKeySafeHandle = ecdsa.DuplicateKeyHandle();
- EventSourceTrace("ECDsa key");
- }
- }
- }
- }
-
- if (privateKeySafeHandle == null || privateKeySafeHandle.IsInvalid)
- {
- EventSourceTrace("Invalid private key");
- return NoCertificateSet;
- }
-
- SafeX509Handle certSafeHandle = Interop.Crypto.X509UpRef(certificate.Handle);
- Interop.Crypto.CheckValidOpenSslHandle(certSafeHandle);
- if (chain != null)
- {
- if (!Interop.Ssl.AddExtraChainCertificates(sslHandle, chain))
- {
- EventSourceTrace("Failed to add extra chain certificate");
- return SuspendHandshake;
- }
- }
-
- certHandle = certSafeHandle.DangerousGetHandle();
- privateKeyHandle = privateKeySafeHandle.DangerousGetHandle();
- EventSourceTrace("Client certificate set: {0}", certificate);
-
- // Ownership has been transferred to OpenSSL; do not free these handles
- certSafeHandle.SetHandleAsInvalid();
- privateKeySafeHandle.SetHandleAsInvalid();
-
- return CertificateSet;
- }
- finally
- {
- if (_clientCertificates == null) certificate?.Dispose(); // only dispose cert if it's automatic / newly created
- chain?.Dispose();
- sslHandle?.Dispose();
- }
- }
-
- public void Dispose()
- {
- _gcHandle.Free();
- }
-
- private static ISet<string> GetRequestCertificateAuthorities(SafeSslHandle sslHandle)
- {
- using (SafeSharedX509NameStackHandle names = Interop.Ssl.SslGetClientCAList(sslHandle))
- {
- if (names.IsInvalid)
- {
- return new HashSet<string>();
- }
-
- int nameCount = Interop.Crypto.GetX509NameStackFieldCount(names);
- var clientAuthorityNames = new HashSet<string>(nameCount);
- for (int i = 0; i < nameCount; i++)
- {
- using (SafeSharedX509NameHandle nameHandle = Interop.Crypto.GetX509NameStackField(names, i))
- {
- X500DistinguishedName dn = Interop.Crypto.LoadX500Name(nameHandle);
- clientAuthorityNames.Add(dn.Name);
- }
- }
- return clientAuthorityNames;
- }
- }
-
- private static bool GetAutomaticClientCertificate(ISet<string> allowedIssuers, out X509Certificate2 certificate, out X509Chain chain)
- {
- using (X509Store myStore = new X509Store(StoreName.My, StoreLocation.CurrentUser))
- {
- // Get the certs from the store.
- myStore.Open(OpenFlags.ReadOnly);
- X509Certificate2Collection certs = myStore.Certificates;
-
- // Find a matching one.
- bool gotCert = certs.TryFindClientCertificate(allowedIssuers, out certificate, out chain);
-
- // Dispose all but the matching cert.
- for (int i = 0; i < certs.Count; i++)
- {
- X509Certificate2 cert = certs[i];
- if (cert != certificate)
- {
- cert.Dispose();
- }
- }
-
- // Return whether we got one.
- return gotCert;
- }
- }
- }
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Diagnostics;
-using System.IO;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace System.Net.Http
-{
- internal partial class CurlHandler : HttpMessageHandler
- {
- private sealed class CurlResponseMessage : HttpResponseMessage
- {
- internal uint _headerBytesReceived;
-
- internal CurlResponseMessage(EasyRequest easy)
- {
- Debug.Assert(easy != null, "Expected non-null EasyRequest");
- RequestMessage = easy._requestMessage;
- ResponseStream = new CurlResponseStream(easy);
- Content = new NoWriteNoSeekStreamContent(ResponseStream);
-
- // On Windows, we pass the equivalent of the easy._cancellationToken
- // in to StreamContent's ctor. This in turn passes that token through
- // to ReadAsync operations on the stream response stream when HttpClient
- // reads from the response stream to buffer it with ResponseContentRead.
- // We don't need to do that here in the Unix implementation as, until the
- // SendAsync task completes, the handler will have registered with the
- // CancellationToken with an action that will cancel all work related to
- // the easy handle if cancellation occurs, and that includes canceling any
- // pending reads on the response stream. It wouldn't hurt anything functionally
- // to still pass easy._cancellationToken here, but it will increase costs
- // a bit, as each read will then need to allocate a larger state object as
- // well as register with and unregister from the cancellation token.
- }
-
- internal CurlResponseStream ResponseStream { get; }
- }
-
- /// <summary>
- /// Provides a response stream that allows libcurl to transfer data asynchronously to a reader.
- /// When writing data to the response stream, either all or none of the data will be transferred,
- /// and if none, libcurl will pause the connection until a reader is waiting to consume the data.
- /// Readers consume the data via ReadAsync, which registers a read state with the stream, to be
- /// filled in by a pending write. Read is just a thin wrapper around ReadAsync, since the underlying
- /// mechanism must be asynchronous to prevent blocking libcurl's processing.
- /// </summary>
- private sealed class CurlResponseStream : Stream
- {
- /// <summary>
- /// A sentinel object used in the <see cref="_completed"/> field to indicate that
- /// the stream completed successfully.
- /// </summary>
- private static readonly Exception s_completionSentinel = new Exception(nameof(s_completionSentinel));
-
- /// <summary>A object used to synchronize all access to state on this response stream.</summary>
- private readonly object _lockObject = new object();
-
- /// <summary>The associated EasyRequest.</summary>
- private readonly EasyRequest _easy;
-
- /// <summary>Stores whether Dispose has been called on the stream.</summary>
- private bool _disposed = false;
-
- /// <summary>
- /// Null if the Stream has not been completed, non-null if it has been completed.
- /// If non-null, it'll either be the <see cref="s_completionSentinel"/> object, meaning the stream completed
- /// successfully, or it'll be an Exception object representing the error that caused completion.
- /// That error will be transferred to any subsequent read requests.
- /// </summary>
- private Exception _completed;
-
- /// <summary>
- /// The state associated with a pending read request. When a reader requests data, it puts
- /// its state here for the writer to fill in when data is available.
- /// </summary>
- private ReadState _pendingReadRequest;
-
- /// <summary>
- /// When data is provided by libcurl, it must be consumed all or nothing: either all of the data is consumed, or
- /// we must pause the connection. Since a read could need to be satisfied with only some of the data provided,
- /// we store the rest here until all reads can consume it. If a subsequent write callback comes in to provide
- /// more data, the connection will then be paused until this buffer is entirely consumed.
- /// </summary>
- private byte[] _remainingData;
-
- /// <summary>
- /// The offset into <see cref="_remainingData"/> from which the next read should occur.
- /// </summary>
- private int _remainingDataOffset;
-
- /// <summary>
- /// The remaining number of bytes in <see cref="_remainingData"/> available to be read.
- /// </summary>
- private int _remainingDataCount;
-
- internal CurlResponseStream(EasyRequest easy)
- {
- Debug.Assert(easy != null, "Expected non-null associated EasyRequest");
- _easy = easy;
- }
-
- public override bool CanRead => !_disposed;
- public override bool CanWrite => false;
- public override bool CanSeek => false;
-
- public override long Length
- {
- get
- {
- CheckDisposed();
- throw new NotSupportedException();
- }
- }
-
- public override long Position
- {
- get
- {
- CheckDisposed();
- throw new NotSupportedException();
- }
-
- set
- {
- CheckDisposed();
- throw new NotSupportedException();
- }
- }
-
- public override long Seek(long offset, SeekOrigin origin)
- {
- CheckDisposed();
- throw new NotSupportedException();
- }
-
- public override void SetLength(long value)
- {
- CheckDisposed();
- throw new NotSupportedException();
- }
-
- public override void Write(byte[] buffer, int offset, int count)
- {
- CheckDisposed();
- throw new NotSupportedException();
- }
-
- public override void Flush()
- {
- // Nothing to do.
- }
-
- /// <summary>
- /// Writes the <paramref name="length"/> bytes starting at <paramref name="pointer"/> to the stream.
- /// </summary>
- /// <returns>
- /// <paramref name="length"/> if all of the data was written, or
- /// <see cref="Interop.Http.CURL_WRITEFUNC_PAUSE"/> if the data wasn't copied and the connection
- /// should be paused until a reader is available.
- /// </returns>
- internal ulong TransferDataToResponseStream(IntPtr pointer, long length)
- {
- Debug.Assert(pointer != IntPtr.Zero, "Expected a non-null pointer");
- Debug.Assert(length >= 0, "Expected a non-negative length");
- EventSourceTrace("Length: {0}", length);
-
- CheckDisposed();
-
- // If there's no data to write, consider everything transferred.
- if (length == 0)
- {
- return 0;
- }
-
- lock (_lockObject)
- {
- VerifyInvariants();
-
- // If there's existing data in the remaining data buffer, or if there's no pending read request,
- // we need to pause until the existing data is consumed or until there's a waiting read.
- if (_remainingDataCount > 0)
- {
- EventSourceTrace("Pausing transfer to response stream. Remaining bytes: {0}", _remainingDataCount);
- return Interop.Http.CURL_WRITEFUNC_PAUSE;
- }
- else if (_pendingReadRequest == null)
- {
- EventSourceTrace("Pausing transfer to response stream. No pending read request");
- return Interop.Http.CURL_WRITEFUNC_PAUSE;
- }
-
- // There's no data in the buffer and there is a pending read request.
- // Transfer as much data as we can to the read request, completing it.
- int numBytesForTask = (int)Math.Min(length, _pendingReadRequest._buffer.Length);
- Debug.Assert(numBytesForTask > 0, "We must be copying a positive amount.");
- unsafe { new Span<byte>((byte*)pointer, numBytesForTask).CopyTo(_pendingReadRequest._buffer.Span); }
- EventSourceTrace("Completing pending read with {0} bytes", numBytesForTask);
- _pendingReadRequest.SetResult(numBytesForTask);
- ClearPendingReadRequest();
-
- // If there's any data left, transfer it to our remaining buffer. libcurl does not support
- // partial transfers of data, so since we just took some of it to satisfy the read request
- // we must take the rest of it. (If libcurl then comes back to us subsequently with more data
- // before this buffered data has been consumed, at that point we won't consume any of the
- // subsequent offering and will ask libcurl to pause.)
- if (numBytesForTask < length)
- {
- IntPtr remainingPointer = pointer + numBytesForTask;
- _remainingDataCount = checked((int)(length - numBytesForTask));
- _remainingDataOffset = 0;
-
- // Make sure our remaining data buffer exists and is big enough to hold the data
- if (_remainingData == null)
- {
- _remainingData = new byte[_remainingDataCount];
- }
- else if (_remainingData.Length < _remainingDataCount)
- {
- _remainingData = new byte[Math.Max(_remainingData.Length * 2, _remainingDataCount)];
- }
-
- // Copy the remaining data to the buffer
- EventSourceTrace("Storing {0} bytes for later", _remainingDataCount);
- Marshal.Copy(remainingPointer, _remainingData, 0, _remainingDataCount);
- }
-
- // All of the data from libcurl was consumed.
- return (ulong)length;
- }
- }
-
- public override int Read(byte[] buffer, int offset, int count) =>
- ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
-
- public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) =>
- TaskToApm.Begin(ReadAsync(buffer, offset, count, CancellationToken.None), callback, state);
-
- public override int EndRead(IAsyncResult asyncResult) =>
- TaskToApm.End<int>(asyncResult);
-
- public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
- {
- if (buffer == null) throw new ArgumentNullException(nameof(buffer));
- if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset));
- if (count < 0) throw new ArgumentOutOfRangeException(nameof(count));
- if (offset > buffer.Length - count) throw new ArgumentException(SR.net_http_buffer_insufficient_length, nameof(buffer));
-
- return ReadAsync(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
- }
-
- public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
- {
- CheckDisposed();
-
- EventSourceTrace("Buffer: {0}", buffer.Length);
-
- // Check for cancellation
- if (cancellationToken.IsCancellationRequested)
- {
- EventSourceTrace("Canceled");
- return new ValueTask<int>(Task.FromCanceled<int>(cancellationToken));
- }
-
- lock (_lockObject)
- {
- VerifyInvariants();
-
- // If there's currently a pending read, fail this read, as we don't support concurrent reads.
- if (_pendingReadRequest != null)
- {
- EventSourceTrace("Failing due to existing pending read; concurrent reads not supported.");
- return new ValueTask<int>(Task.FromException<int>(new InvalidOperationException(SR.net_http_content_no_concurrent_reads)));
- }
-
- // If the stream was already completed with failure, complete the read as a failure.
- if (_completed != null && _completed != s_completionSentinel)
- {
- EventSourceTrace("Failing read with error: {0}", _completed);
-
- OperationCanceledException oce = _completed as OperationCanceledException;
- return new ValueTask<int>((oce != null && oce.CancellationToken.IsCancellationRequested) ?
- Task.FromCanceled<int>(oce.CancellationToken) :
- Task.FromException<int>(MapToReadWriteIOException(_completed, isRead: true)));
- }
-
- // Quick check for if no data was actually requested. We do this after the check
- // for errors so that we can still fail the read and transfer the exception if we should.
- if (buffer.Length == 0)
- {
- return new ValueTask<int>(0);
- }
-
- // If there's any data left over from a previous call, grab as much as we can.
- if (_remainingDataCount > 0)
- {
- int bytesToCopy = Math.Min(buffer.Length, _remainingDataCount);
- new Span<byte>(_remainingData, _remainingDataOffset, bytesToCopy).CopyTo(buffer.Span);
-
- _remainingDataOffset += bytesToCopy;
- _remainingDataCount -= bytesToCopy;
- Debug.Assert(_remainingDataCount >= 0, "The remaining count should never go negative");
- Debug.Assert(_remainingDataOffset <= _remainingData.Length, "The remaining offset should never exceed the buffer size");
-
- EventSourceTrace("Read {0} bytes", bytesToCopy);
- return new ValueTask<int>(bytesToCopy);
- }
-
- // If the stream has already been completed, complete the read immediately.
- if (_completed == s_completionSentinel)
- {
- EventSourceTrace("Stream already completed");
- return new ValueTask<int>(0);
- }
-
- // Finally, the stream is still alive, and we want to read some data, but there's no data
- // in the buffer so we need to register ourself to get the next write.
- if (cancellationToken.CanBeCanceled)
- {
- // If the cancellation token is cancelable, then we need to register for cancellation.
- // We create a special CancelableReadState that carries with it additional info:
- // the cancellation token and the registration with that token. When cancellation
- // is requested, we schedule a work item that tries to remove the read state
- // from being pending, canceling it in the process. This needs to happen under the
- // lock, which is why we schedule the operation to run asynchronously: if it ran
- // synchronously, it could deadlock due to code on another thread holding the lock
- // and calling Dispose on the registration concurrently with the call to Cancel
- // the cancellation token. Dispose on the registration won't return until the action
- // associated with the registration has completed, but if that action is currently
- // executing and is blocked on the lock that's held while calling Dispose... deadlock.
- var crs = new CancelableReadState(buffer, this, cancellationToken);
- crs._registration = cancellationToken.Register(s1 =>
- {
- ((CancelableReadState)s1)._stream.EventSourceTrace("Cancellation invoked. Queueing work item to cancel read state");
- Task.Factory.StartNew(s2 =>
- {
- var crsRef = (CancelableReadState)s2;
- lock (crsRef._stream._lockObject)
- {
- Debug.Assert(crsRef._token.IsCancellationRequested, "We should only be here if cancellation was requested.");
- if (crsRef._stream._pendingReadRequest == crsRef)
- {
- crsRef._stream.EventSourceTrace("Canceling");
- crsRef.TrySetCanceled(crsRef._token);
- crsRef._stream.ClearPendingReadRequest();
- }
- }
- }, s1, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
- }, crs);
- _pendingReadRequest = crs;
- }
- else
- {
- // The token isn't cancelable. Just create a normal read state.
- _pendingReadRequest = new ReadState(buffer);
- }
-
- _easy._associatedMultiAgent.RequestUnpause(_easy);
- _easy._selfStrongToWeakReference.MakeStrong(); // convert from a weak to a strong ref to keep the easy alive during the read
- return new ValueTask<int>(_pendingReadRequest.Task);
- }
- }
-
- /// <summary>Notifies the stream that no more data will be written.</summary>
- internal void SignalComplete(Exception error = null, bool forceCancel = false)
- {
- lock (_lockObject)
- {
- VerifyInvariants();
-
- // If we already completed, nothing more to do
- if (_completed != null)
- {
- EventSourceTrace("Already completed");
- return;
- }
-
- // Mark ourselves as being completed
- EventSourceTrace("Marking as complete");
- _completed = error ?? s_completionSentinel;
-
- // If the request wasn't already completed, and if requested, send a cancellation
- // request to ensure that the connection gets cleaned up. This is only necessary
- // to do if this method is being called for a reason other than the request/response
- // completing naturally, e.g. if the response stream is being disposed of before
- // all of the response has been downloaded.
- if (forceCancel)
- {
- EventSourceTrace("Completing the response stream prematurely.");
- _easy._associatedMultiAgent.RequestCancel(_easy);
- }
-
- // If there's a pending read request, complete it, either with 0 bytes for success
- // or with the exception/CancellationToken for failure.
- ReadState pendingRead = _pendingReadRequest;
- if (pendingRead != null)
- {
- if (_completed == s_completionSentinel)
- {
- EventSourceTrace("Completing pending read with 0 bytes");
- pendingRead.TrySetResult(0);
- }
- else
- {
- EventSourceTrace("Failing pending read task with error: {0}", _completed);
- OperationCanceledException oce = _completed as OperationCanceledException;
- if (oce != null)
- {
- pendingRead.TrySetCanceled(oce.CancellationToken);
- }
- else
- {
- pendingRead.TrySetException(MapToReadWriteIOException(_completed, isRead: true));
- }
- }
-
- ClearPendingReadRequest();
- }
- }
- }
-
- /// <summary>
- /// Clears a pending read request, making sure any cancellation registration is unregistered and
- /// ensuring that the EasyRequest has dropped its strong reference to itself, which should only
- /// exist while an active async operation is going.
- /// </summary>
- private void ClearPendingReadRequest()
- {
- Debug.Assert(Monitor.IsEntered(_lockObject), "Lock object must be held to manipulate _pendingReadRequest");
- Debug.Assert(_pendingReadRequest != null, "Should only be clearing the pending read request if there is one");
- Debug.Assert(_pendingReadRequest.Task.IsCompleted, "The pending request should have been completed");
-
- (_pendingReadRequest as CancelableReadState)?._registration.Dispose();
- _pendingReadRequest = null;
-
- // The async operation has completed. We no longer want to be holding a strong reference.
- _easy._selfStrongToWeakReference.MakeWeak();
- }
-
- ~CurlResponseStream()
- {
- Dispose(disposing: false);
- }
-
- protected override void Dispose(bool disposing)
- {
- // disposing is ignored. We need to SignalComplete whether this is due to Dispose
- // or due to finalization, so that we don't leave Tasks uncompleted, don't leave
- // connections paused, etc.
-
- if (!_disposed)
- {
- EventSourceTrace("Disposing response stream");
- _disposed = true;
- SignalComplete(forceCancel: true);
- }
-
- base.Dispose(disposing);
- }
-
- private void CheckDisposed()
- {
- if (_disposed)
- {
- throw new ObjectDisposedException(GetType().FullName);
- }
- }
-
- private void EventSourceTrace<TArg0>(string formatMessage, TArg0 arg0, [CallerMemberName] string memberName = null)
- {
- CurlHandler.EventSourceTrace(formatMessage, arg0, agent: null, easy: _easy, memberName: memberName);
- }
-
- private void EventSourceTrace<TArg0, TArg1, TArg2>(string formatMessage, TArg0 arg0, TArg1 arg1, TArg2 arg2, [CallerMemberName] string memberName = null)
- {
- CurlHandler.EventSourceTrace(formatMessage, arg0, arg1, arg2, agent: null, easy: _easy, memberName: memberName);
- }
-
- private void EventSourceTrace(string message, [CallerMemberName] string memberName = null)
- {
- CurlHandler.EventSourceTrace(message, agent: null, easy: _easy, memberName: memberName);
- }
-
- /// <summary>Verifies various invariants that must be true about our state.</summary>
- [Conditional("DEBUG")]
- private void VerifyInvariants()
- {
- Debug.Assert(Monitor.IsEntered(_lockObject), "Can only verify invariants while holding the lock");
-
- Debug.Assert(_remainingDataCount >= 0, "Remaining data count should never be negative.");
- Debug.Assert(_remainingDataCount == 0 || _remainingData != null, "If there's remaining data, there must be a buffer to store it.");
- Debug.Assert(_remainingData == null || _remainingDataCount <= _remainingData.Length, "The buffer must be large enough for the data length.");
- Debug.Assert(_remainingData == null || _remainingDataOffset <= _remainingData.Length, "The offset must not walk off the buffer.");
-
- Debug.Assert(!((_remainingDataCount > 0) && (_pendingReadRequest != null)), "We can't have both remaining data and a pending request.");
- Debug.Assert(!((_completed != null) && (_pendingReadRequest != null)), "We can't both be completed and have a pending request.");
- Debug.Assert(_pendingReadRequest == null || !_pendingReadRequest.Task.IsCompleted, "A pending read request must not have been completed yet.");
- }
-
- /// <summary>State associated with a pending read request.</summary>
- private class ReadState : TaskCompletionSource<int>
- {
- internal readonly Memory<byte> _buffer;
-
- internal ReadState(Memory<byte> buffer) : base(TaskCreationOptions.RunContinuationsAsynchronously)
- {
- _buffer = buffer;
- }
- }
-
- /// <summary>State associated with a pending read request that's cancelable.</summary>
- private sealed class CancelableReadState : ReadState
- {
- internal readonly CurlResponseStream _stream;
- internal readonly CancellationToken _token;
- internal CancellationTokenRegistration _registration;
-
- internal CancelableReadState(Memory<byte> buffer, CurlResponseStream responseStream, CancellationToken cancellationToken) : base(buffer)
- {
- _stream = responseStream;
- _token = cancellationToken;
- }
- }
- }
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Buffers;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Net.Http.Headers;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Threading;
-using System.Threading.Tasks;
-
-using CURLAUTH = Interop.Http.CURLAUTH;
-using CURLcode = Interop.Http.CURLcode;
-using CURLoption = Interop.Http.CURLoption;
-using CurlProtocols = Interop.Http.CurlProtocols;
-using CURLProxyType = Interop.Http.curl_proxytype;
-using SafeCurlHandle = Interop.Http.SafeCurlHandle;
-using SafeCurlSListHandle = Interop.Http.SafeCurlSListHandle;
-using SafeCallbackHandle = Interop.Http.SafeCallbackHandle;
-using SeekCallback = Interop.Http.SeekCallback;
-using ReadWriteCallback = Interop.Http.ReadWriteCallback;
-using ReadWriteFunction = Interop.Http.ReadWriteFunction;
-using SslCtxCallback = Interop.Http.SslCtxCallback;
-using DebugCallback = Interop.Http.DebugCallback;
-
-namespace System.Net.Http
-{
- internal partial class CurlHandler : HttpMessageHandler
- {
- /// <summary>Provides all of the state associated with a single request/response, referred to as an "easy" request in libcurl parlance.</summary>
- private sealed class EasyRequest : TaskCompletionSource<HttpResponseMessage>
- {
- /// <summary>Maximum content length where we'll use COPYPOSTFIELDS to let libcurl dup the content.</summary>
- private const int InMemoryPostContentLimit = 32 * 1024; // arbitrary limit; could be tweaked in the future based on experimentation
- /// <summary>Debugging flag used to enable CURLOPT_VERBOSE to dump to stderr when not redirecting it to the event source.</summary>
- private static readonly bool s_curlDebugLogging = Environment.GetEnvironmentVariable("CURLHANDLER_DEBUG_VERBOSE") == "true";
-
- internal readonly CurlHandler _handler;
- internal readonly MultiAgent _associatedMultiAgent;
- internal readonly HttpRequestMessage _requestMessage;
- internal readonly CurlResponseMessage _responseMessage;
- internal readonly CancellationToken _cancellationToken;
- internal Stream _requestContentStream;
- internal long? _requestContentStreamStartingPosition;
- internal bool _inMemoryPostContent;
-
- internal SafeCurlHandle _easyHandle;
- private SafeCurlSListHandle _requestHeaders;
-
- internal SendTransferState _sendTransferState;
- internal StrongToWeakReference<EasyRequest> _selfStrongToWeakReference;
-
- private SafeCallbackHandle _callbackHandle;
-
- public EasyRequest(CurlHandler handler, MultiAgent agent, HttpRequestMessage requestMessage, CancellationToken cancellationToken) :
- base(TaskCreationOptions.RunContinuationsAsynchronously)
- {
- Debug.Assert(handler != null, $"Expected non-null {nameof(handler)}");
- Debug.Assert(agent != null, $"Expected non-null {nameof(agent)}");
- Debug.Assert(requestMessage != null, $"Expected non-null {nameof(requestMessage)}");
-
- _handler = handler;
- _associatedMultiAgent = agent;
- _requestMessage = requestMessage;
-
- _cancellationToken = cancellationToken;
- _responseMessage = new CurlResponseMessage(this);
- }
-
- /// <summary>
- /// Initialize the underlying libcurl support for this EasyRequest.
- /// This is separated out of the constructor so that we can take into account
- /// any additional configuration needed based on the request message
- /// after the EasyRequest is configured and so that error handling
- /// can be better handled in the caller.
- /// </summary>
- internal void InitializeCurl()
- {
- // Create the underlying easy handle
- SafeCurlHandle easyHandle = Interop.Http.EasyCreate();
- if (easyHandle.IsInvalid)
- {
- throw new OutOfMemoryException();
- }
- _easyHandle = easyHandle;
-
- EventSourceTrace("Configuring request.");
-
- // Before setting any other options, turn on curl's debug tracing
- // if desired. CURLOPT_VERBOSE may also be set subsequently if
- // EventSource tracing is enabled.
- if (s_curlDebugLogging)
- {
- SetCurlOption(CURLoption.CURLOPT_VERBOSE, 1L);
- }
-
- // Before actually configuring the handle based on the state of the request,
- // do any necessary cleanup of the request object.
- SanitizeRequestMessage();
-
- // Configure the handle
- SetUrl();
- SetNetworkingOptions();
- SetMultithreading();
- SetTimeouts();
- SetRedirection();
- SetVerb();
- SetVersion();
- SetDecompressionOptions();
- SetProxyOptions(_requestMessage.RequestUri);
- SetCredentialsOptions(_handler._useDefaultCredentials ? GetDefaultCredentialAndAuth() : _handler.GetCredentials(_requestMessage.RequestUri));
- SetCookieOption(_requestMessage.RequestUri);
- SetRequestHeaders(copyAuthHeaders:true);
- SetSslOptions();
-
- EventSourceTrace("Done configuring request.");
- }
-
- public void EnsureResponseMessagePublished()
- {
- // If the response message hasn't been published yet, do any final processing of it before it is.
- if (!Task.IsCompleted)
- {
- EventSourceTrace("Publishing response message");
-
- // On Windows, if the response was automatically decompressed, Content-Encoding and Content-Length
- // headers are removed from the response. Do the same thing here.
- DecompressionMethods dm = _handler.AutomaticDecompression;
- if (dm != DecompressionMethods.None)
- {
- HttpContentHeaders contentHeaders = _responseMessage.Content.Headers;
- IEnumerable<string> encodings;
- if (contentHeaders.TryGetValues(HttpKnownHeaderNames.ContentEncoding, out encodings))
- {
- foreach (string encoding in encodings)
- {
- if (((dm & DecompressionMethods.GZip) != 0 && string.Equals(encoding, EncodingNameGzip, StringComparison.OrdinalIgnoreCase)) ||
- ((dm & DecompressionMethods.Deflate) != 0 && string.Equals(encoding, EncodingNameDeflate, StringComparison.OrdinalIgnoreCase)))
- {
- contentHeaders.Remove(HttpKnownHeaderNames.ContentEncoding);
- contentHeaders.Remove(HttpKnownHeaderNames.ContentLength);
- break;
- }
- }
- }
- }
- }
-
- // Now ensure it's published.
- bool completedTask = TrySetResult(_responseMessage);
- Debug.Assert(completedTask || Task.IsCompletedSuccessfully,
- "If the task was already completed, it should have been completed successfully; " +
- "we shouldn't be completing as successful after already completing as failed.");
-
- // If we successfully transitioned it to be completed, we also handed off lifetime ownership
- // of the response to the owner of the task. Transition our reference on the EasyRequest
- // to be weak instead of strong, so that we don't forcibly keep it alive.
- if (completedTask)
- {
- Debug.Assert(_selfStrongToWeakReference != null, "Expected non-null wrapper");
- _selfStrongToWeakReference.MakeWeak();
- }
- }
-
- public void CleanupAndFailRequest(Exception error)
- {
- try
- {
- Cleanup();
- }
- catch (Exception exc)
- {
- // This would only happen in an aggregious case, such as a Stream failing to seek when
- // it claims to be able to, but in case something goes very wrong, make sure we don't
- // lose the exception information.
- error = new AggregateException(error, exc);
- }
- finally
- {
- FailRequest(error);
- }
- }
-
- public void FailRequest(Exception error)
- {
- Debug.Assert(error != null, "Expected non-null exception");
- EventSourceTrace("Failing request: {0}", error);
-
- var oce = error as OperationCanceledException;
- if (oce != null)
- {
- TrySetCanceled(oce.CancellationToken);
- }
- else
- {
- if (error is InvalidOperationException || error is IOException || error is CurlException || error == null)
- {
- error = CreateHttpRequestException(error);
- }
- TrySetException(error);
- }
- // There's not much we can reasonably assert here about the result of TrySet*.
- // It's possible that the task wasn't yet completed (e.g. a failure while initiating the request),
- // it's possible that the task was already completed as success (e.g. a failure sending back the response),
- // and it's possible that the task was already completed as failure (e.g. we handled the exception and
- // faulted the task, but then tried to fault it again while finishing processing in the main loop).
-
- // Make sure the exception is available on the response stream so that it's propagated
- // from any attempts to read from the stream.
- _responseMessage.ResponseStream.SignalComplete(error);
- }
-
- public void Cleanup() // not called Dispose because the request may still be in use after it's cleaned up
- {
- // Don't dispose of the ResponseMessage.ResponseStream as it may still be in use
- // by code reading data stored in the stream. Also don't dispose of the request content
- // stream; that'll be handled by the disposal of the request content by the HttpClient,
- // and doing it here prevents reuse by an intermediate handler sitting between the client
- // and this handler.
-
- // However, if we got an original position for the request stream, we seek back to that position,
- // for the corner case where the stream does get reused before it's disposed by the HttpClient
- // (if the same request object is used multiple times from an intermediate handler, we'll be using
- // ReadAsStreamAsync, which on the same request object will return the same stream object, which
- // we've already advanced).
- if (_requestContentStream != null && _requestContentStream.CanSeek)
- {
- Debug.Assert(_requestContentStreamStartingPosition.HasValue, "The stream is seekable, but we don't have a starting position?");
- _requestContentStream.Position = _requestContentStreamStartingPosition.GetValueOrDefault();
- }
-
- // Dispose of the underlying easy handle. We're no longer processing it.
- _easyHandle?.Dispose();
-
- // Dispose of the request headers if we had any. We had to keep this handle
- // alive as long as the easy handle was using it. We didn't need to do any
- // ref counting on the safe handle, though, as the only processing happens
- // in Process, which ensures the handle will be rooted while libcurl is
- // doing any processing that assumes it's valid.
- _requestHeaders?.Dispose();
-
- // Dispose native callback resources
- _callbackHandle?.Dispose();
-
- // Release any send transfer state, which will return its buffer to the pool
- _sendTransferState?.Dispose();
- }
-
- private void SanitizeRequestMessage()
- {
- // Make sure Transfer-Encoding and Content-Length make sense together.
- if (_requestMessage.Content != null)
- {
- SetChunkedModeForSend(_requestMessage);
- }
- }
-
- private void SetUrl()
- {
- Uri requestUri = _requestMessage.RequestUri;
-
- long scopeId;
- string url;
-
- EventSourceTrace("Url: {0}", requestUri);
- SetCurlOption(CURLoption.CURLOPT_PROTOCOLS, (long)(CurlProtocols.CURLPROTO_HTTP | CurlProtocols.CURLPROTO_HTTPS));
-
- if (IsLinkLocal(requestUri, out scopeId))
- {
- // Uri.AbsoluteUri doesn't include the ScopeId/ZoneID, so if it is link-local,
- // we separately pass the scope to libcurl.
- EventSourceTrace("ScopeId: {0}", scopeId);
- try
- {
- SetCurlOption(CURLoption.CURLOPT_ADDRESS_SCOPE, scopeId);
- }
- catch (CurlException)
- {
- // libCurl has a bug regarding long scope-ids:
- // https://github.com/curl/curl/issues/3713
- // Workaround the problem by percent-encoding the scope separator ('%')
- url = new UriBuilder(requestUri) { Host = requestUri.IdnHost}.ToString().Replace("%", "%25");
- SetCurlOption(CURLoption.CURLOPT_URL, url);
- return;
- }
- }
-
- string idnHost = requestUri.IdnHost;
- url = requestUri.Host == idnHost ?
- requestUri.AbsoluteUri :
- new UriBuilder(requestUri) { Host = idnHost }.Uri.AbsoluteUri;
-
- SetCurlOption(CURLoption.CURLOPT_URL, url);
- }
-
- private static bool IsLinkLocal(Uri url, out long scopeId)
- {
- IPAddress ip;
- if (IPAddress.TryParse(url.DnsSafeHost, out ip) && ip.IsIPv6LinkLocal)
- {
- scopeId = ip.ScopeId;
- return true;
- }
-
- scopeId = 0;
- return false;
- }
-
- private void SetNetworkingOptions()
- {
- // Disable the TCP Nagle algorithm. It's disabled by default starting with libcurl 7.50.2,
- // and when enabled has a measurably negative impact on latency in key scenarios
- // (e.g. POST'ing small-ish data).
- SetCurlOption(CURLoption.CURLOPT_TCP_NODELAY, 1L);
- // Enable TCP keep-alive.
- SetCurlOption(CURLoption.CURLOPT_TCP_KEEPALIVE, 1L);
- }
-
- private void SetMultithreading()
- {
- SetCurlOption(CURLoption.CURLOPT_NOSIGNAL, 1L);
- }
-
- private void SetTimeouts()
- {
- // Set timeout limit on the connect phase. curl has bug on ARM so use max - 1s.
- SetCurlOption(CURLoption.CURLOPT_CONNECTTIMEOUT_MS, int.MaxValue - 1000);
-
- // Override the default DNS cache timeout. libcurl defaults to a 1 minute
- // timeout, but we extend that to match the Windows timeout of 10 minutes.
- const int DnsCacheTimeoutSeconds = 10 * 60;
- SetCurlOption(CURLoption.CURLOPT_DNS_CACHE_TIMEOUT, DnsCacheTimeoutSeconds);
- }
-
- private void SetRedirection()
- {
- if (!_handler._automaticRedirection)
- {
- return;
- }
-
- SetCurlOption(CURLoption.CURLOPT_FOLLOWLOCATION, 1L);
-
- CurlProtocols redirectProtocols = string.Equals(_requestMessage.RequestUri.Scheme, UriSchemeHttps, StringComparison.OrdinalIgnoreCase) ?
- CurlProtocols.CURLPROTO_HTTPS : // redirect only to another https
- CurlProtocols.CURLPROTO_HTTP | CurlProtocols.CURLPROTO_HTTPS; // redirect to http or to https
- SetCurlOption(CURLoption.CURLOPT_REDIR_PROTOCOLS, (long)redirectProtocols);
-
- SetCurlOption(CURLoption.CURLOPT_MAXREDIRS, _handler._maxAutomaticRedirections);
- EventSourceTrace("Max automatic redirections: {0}", _handler._maxAutomaticRedirections);
- }
-
- /// <summary>
- /// When a Location header is received along with a 3xx status code, it's an indication
- /// that we're likely to redirect. Prepare the easy handle in case we do.
- /// </summary>
- internal void SetPossibleRedirectForLocationHeader(string location)
- {
- // Reset cookies in case we redirect. Below we'll set new cookies for the
- // new location if we have any.
- if (_handler._useCookies)
- {
- SetCurlOption(CURLoption.CURLOPT_COOKIE, IntPtr.Zero);
- }
-
- // Parse the location string into a relative or absolute Uri, then combine that
- // with the current request Uri to get the new location.
- var updatedCredentials = default(KeyValuePair<NetworkCredential, CURLAUTH>);
- Uri newUri;
- if (Uri.TryCreate(_requestMessage.RequestUri, location.Trim(), out newUri))
- {
- // Just as with WinHttpHandler, for security reasons, we drop the server credential if it is
- // anything other than a CredentialCache. We allow credentials in a CredentialCache since they
- // are specifically tied to URIs.
- updatedCredentials = _handler._useDefaultCredentials ?
- GetDefaultCredentialAndAuth() :
- GetCredentials(newUri, _handler.Credentials as CredentialCache, s_orderedAuthTypes);
-
- // Reset proxy - it is possible that the proxy has different credentials for the new URI
- SetProxyOptions(newUri);
-
- // Set up new cookies
- if (_handler._useCookies)
- {
- SetCookieOption(newUri);
- }
-
- if (newUri.Scheme == Uri.UriSchemeHttp && _requestMessage.RequestUri.Scheme == Uri.UriSchemeHttps)
- {
- EventSourceTrace("Insecure https to http redirect: {0}", (_requestMessage.RequestUri, newUri));
- }
- }
-
- // Set up the new credentials, either for the new Uri if we were able to get it,
- // or to empty creds if we couldn't.
- SetCredentialsOptions(updatedCredentials);
-
- // Set the headers again. This is a workaround for libcurl's limitation in handling
- // headers with empty values.
- SetRequestHeaders(copyAuthHeaders:false);
- }
-
- private void SetContentLength(CURLoption lengthOption)
- {
- Debug.Assert(lengthOption == CURLoption.CURLOPT_POSTFIELDSIZE || lengthOption == CURLoption.CURLOPT_INFILESIZE);
-
- if (_requestMessage.Content == null)
- {
- // Tell libcurl there's no data to be sent.
- SetCurlOption(lengthOption, 0L);
- return;
- }
-
- long? contentLengthOpt = _requestMessage.Content.Headers.ContentLength;
- if (contentLengthOpt != null)
- {
- long contentLength = contentLengthOpt.GetValueOrDefault();
- if (contentLength <= int.MaxValue)
- {
- // Tell libcurl how much data we expect to send.
- SetCurlOption(lengthOption, contentLength);
- }
- else
- {
- // Similarly, tell libcurl how much data we expect to send. However,
- // as the amount is larger than a 32-bit value, switch to the "_LARGE"
- // equivalent libcurl options.
- SetCurlOption(
- lengthOption == CURLoption.CURLOPT_INFILESIZE ? CURLoption.CURLOPT_INFILESIZE_LARGE : CURLoption.CURLOPT_POSTFIELDSIZE_LARGE,
- contentLength);
- }
- EventSourceTrace("Set content length: {0}", contentLength);
- return;
- }
-
- // There is content but we couldn't determine its size. Don't set anything.
- }
-
- private void SetVerb()
- {
- EventSourceTrace<string>("Verb: {0}", _requestMessage.Method.Method);
-
- if (_requestMessage.Method == HttpMethod.Put)
- {
- SetCurlOption(CURLoption.CURLOPT_UPLOAD, 1L);
- SetContentLength(CURLoption.CURLOPT_INFILESIZE);
- }
- else if (_requestMessage.Method == HttpMethod.Head)
- {
- SetCurlOption(CURLoption.CURLOPT_NOBODY, 1L);
- }
- else if (_requestMessage.Method == HttpMethod.Post)
- {
- SetCurlOption(CURLoption.CURLOPT_POST, 1L);
-
- // Set the content length if we have one available. We must set POSTFIELDSIZE before setting
- // COPYPOSTFIELDS, as the setting of COPYPOSTFIELDS uses the size to know how much data to read
- // out; if POSTFIELDSIZE is not done before, COPYPOSTFIELDS will look for a null terminator, and
- // we don't necessarily have one.
- SetContentLength(CURLoption.CURLOPT_POSTFIELDSIZE);
-
- // For most content types and most HTTP methods, we use a callback that lets libcurl
- // get data from us if/when it wants it. However, as an optimization, for POSTs that
- // use content already known to be entirely in memory, we hand that data off to libcurl
- // ahead of time. This not only saves on costs associated with all of the async transfer
- // between the content and libcurl, it also lets libcurl do larger writes that can, for
- // example, enable fewer packets to be sent on the wire.
- var inMemContent = _requestMessage.Content as ByteArrayContent;
- ArraySegment<byte> contentSegment;
- if (inMemContent != null &&
- inMemContent.TryGetBuffer(out contentSegment) &&
- contentSegment.Count <= InMemoryPostContentLimit) // skip if we'd be forcing libcurl to allocate/copy a large buffer
- {
- // Only pre-provide the content if the content still has its ContentLength
- // and if that length matches the segment. If it doesn't, something has been overridden,
- // and we should rely on reading from the content stream to get the data.
- long? contentLength = inMemContent.Headers.ContentLength;
- if (contentLength.HasValue && contentLength.GetValueOrDefault() == contentSegment.Count)
- {
- _inMemoryPostContent = true;
-
- // Debug double-check array segment; this should all have been validated by the ByteArrayContent
- Debug.Assert(contentSegment.Array != null, "Expected non-null byte content array");
- Debug.Assert(contentSegment.Count >= 0, $"Expected non-negative byte content count {contentSegment.Count}");
- Debug.Assert(contentSegment.Offset >= 0, $"Expected non-negative byte content offset {contentSegment.Offset}");
- Debug.Assert(contentSegment.Array.Length - contentSegment.Offset >= contentSegment.Count,
- $"Expected offset {contentSegment.Offset} + count {contentSegment.Count} to be within array length {contentSegment.Array.Length}");
-
- // Hand the data off to libcurl with COPYPOSTFIELDS for it to copy out the data. (The alternative
- // is to use POSTFIELDS, which would mean we'd need to pin the array in the ByteArrayContent for the
- // duration of the request. Often with a ByteArrayContent, the data will be small and the copy cheap.)
- unsafe
- {
- fixed (byte* inMemContentPtr = contentSegment.Array)
- {
- SetCurlOption(CURLoption.CURLOPT_COPYPOSTFIELDS, new IntPtr(inMemContentPtr + contentSegment.Offset));
- EventSourceTrace("Set post fields rather than using send content callback");
- }
- }
- }
- }
- }
- else if (_requestMessage.Method == HttpMethod.Trace)
- {
- SetCurlOption(CURLoption.CURLOPT_CUSTOMREQUEST, _requestMessage.Method.Method);
- SetCurlOption(CURLoption.CURLOPT_NOBODY, 1L);
- }
- else
- {
- SetCurlOption(CURLoption.CURLOPT_CUSTOMREQUEST, _requestMessage.Method.Method);
- if (_requestMessage.Content != null)
- {
- SetCurlOption(CURLoption.CURLOPT_UPLOAD, 1L);
- SetContentLength(CURLoption.CURLOPT_INFILESIZE);
- }
- }
- }
-
- private void SetVersion()
- {
- Version v = _requestMessage.Version;
- if (v != null)
- {
- // Try to use the requested version, if a known version was explicitly requested.
- // If an unknown version was requested, we simply use libcurl's default.
- // Only allow HTTP/2 when making https requests.
- var curlVersion =
- (v.Major == 1 && v.Minor == 1) ? Interop.Http.CurlHttpVersion.CURL_HTTP_VERSION_1_1 :
- (v.Major == 1 && v.Minor == 0) ? Interop.Http.CurlHttpVersion.CURL_HTTP_VERSION_1_0 :
- (v.Major == 2 && v.Minor == 0) ? Interop.Http.CurlHttpVersion.CURL_HTTP_VERSION_2TLS :
- Interop.Http.CurlHttpVersion.CURL_HTTP_VERSION_NONE;
-
- if (curlVersion != Interop.Http.CurlHttpVersion.CURL_HTTP_VERSION_NONE)
- {
- // Ask libcurl to use the specified version if possible.
- CURLcode c = Interop.Http.EasySetOptionLong(_easyHandle, CURLoption.CURLOPT_HTTP_VERSION, (long)curlVersion);
- if (c == CURLcode.CURLE_OK)
- {
- // Success. The requested version will be used.
- EventSourceTrace("HTTP version: {0}", v);
- }
- else if (c == CURLcode.CURLE_UNSUPPORTED_PROTOCOL)
- {
- // The requested version is unsupported. Fall back to using the default version chosen by libcurl.
- EventSourceTrace("Unsupported protocol: {0}", v);
- }
- else
- {
- // Some other error. Fail.
- ThrowIfCURLEError(c);
- }
- }
- }
- }
-
- private void SetDecompressionOptions()
- {
- if (!_handler.SupportsAutomaticDecompression)
- {
- return;
- }
-
- DecompressionMethods autoDecompression = _handler.AutomaticDecompression;
- bool gzip = (autoDecompression & DecompressionMethods.GZip) != 0;
- bool deflate = (autoDecompression & DecompressionMethods.Deflate) != 0;
- if (gzip || deflate)
- {
- string encoding = (gzip && deflate) ? EncodingNameGzip + "," + EncodingNameDeflate :
- gzip ? EncodingNameGzip :
- EncodingNameDeflate;
- SetCurlOption(CURLoption.CURLOPT_ACCEPT_ENCODING, encoding);
- EventSourceTrace<string>("Encoding: {0}", encoding);
- }
- }
-
- internal void SetProxyOptions(Uri requestUri)
- {
- if (!_handler._useProxy)
- {
- // Explicitly disable the use of a proxy. This will prevent libcurl from using
- // any proxy, including ones set via environment variable.
- SetCurlOption(CURLoption.CURLOPT_PROXY, string.Empty);
- EventSourceTrace("UseProxy false, disabling proxy");
- return;
- }
-
- if (_handler.Proxy == null)
- {
- // UseProxy was true, but Proxy was null. Let libcurl do its default handling,
- // which includes checking the http_proxy environment variable.
- EventSourceTrace("UseProxy true, Proxy null, using default proxy");
-
- // Since that proxy set in an environment variable might require a username and password,
- // use the default proxy credentials if there are any. Currently only NetworkCredentials
- // are used, as we can't query by the proxy Uri, since we don't know it.
- SetProxyCredentials(_handler.DefaultProxyCredentials as NetworkCredential);
-
- return;
- }
-
- // Custom proxy specified.
- Uri proxyUri;
- try
- {
- // Should we bypass a proxy for this URI?
- if (_handler.Proxy.IsBypassed(requestUri))
- {
- SetCurlOption(CURLoption.CURLOPT_PROXY, string.Empty);
- EventSourceTrace("Proxy's IsBypassed returned true, bypassing proxy");
- return;
- }
-
- // Get the proxy Uri for this request.
- proxyUri = _handler.Proxy.GetProxy(requestUri);
- if (proxyUri == null)
- {
- EventSourceTrace("GetProxy returned null, using default.");
- return;
- }
- }
- catch (PlatformNotSupportedException)
- {
- // WebRequest.DefaultWebProxy throws PlatformNotSupportedException,
- // in which case we should use the default rather than the custom proxy.
- EventSourceTrace("PlatformNotSupportedException from proxy, using default");
- return;
- }
-
- // Configure libcurl with the gathered proxy information
-
- // uri.AbsoluteUri/ToString() omit IPv6 scope IDs. SerializationInfoString ensures these details
- // are included, but does not properly handle international hosts. As a workaround we check whether
- // the host is a link-local IP address, and based on that either return the SerializationInfoString
- // or the AbsoluteUri. (When setting the request Uri itself, we instead use CURLOPT_ADDRESS_SCOPE to
- // set the scope id and the url separately, avoiding these issues and supporting versions of libcurl
- // prior to v7.37 that don't support parsing scope IDs out of the url's host. As similar feature
- // doesn't exist for proxies.)
- IPAddress ip;
- string proxyUrl = IPAddress.TryParse(proxyUri.DnsSafeHost, out ip) && ip.IsIPv6LinkLocal ?
- proxyUri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped) :
- proxyUri.AbsoluteUri;
-
- EventSourceTrace<string>("Proxy: {0}", proxyUrl);
- SetCurlOption(CURLoption.CURLOPT_PROXYTYPE, (long)CURLProxyType.CURLPROXY_HTTP);
- SetCurlOption(CURLoption.CURLOPT_PROXY, proxyUrl);
- SetCurlOption(CURLoption.CURLOPT_PROXYPORT, proxyUri.Port);
-
- KeyValuePair<NetworkCredential, CURLAUTH> credentialScheme = GetCredentials(
- proxyUri, _handler.Proxy.Credentials, s_orderedAuthTypes);
- SetProxyCredentials(credentialScheme.Key);
- }
-
- private void SetProxyCredentials(NetworkCredential credentials)
- {
- if (credentials == CredentialCache.DefaultCredentials)
- {
- EventSourceTrace("DefaultCredentials set for proxy. Skipping.");
- }
- else if (credentials != null)
- {
- if (string.IsNullOrEmpty(credentials.UserName))
- {
- throw new ArgumentException(SR.net_http_argument_empty_string, "UserName");
- }
-
- // Unlike normal credentials, proxy credentials are URL decoded by libcurl, so we URL encode
- // them in order to allow, for example, a colon in the username.
- string credentialText = string.IsNullOrEmpty(credentials.Domain) ?
- WebUtility.UrlEncode(credentials.UserName) + ":" + WebUtility.UrlEncode(credentials.Password) :
- string.Format("{2}\\{0}:{1}", WebUtility.UrlEncode(credentials.UserName), WebUtility.UrlEncode(credentials.Password), WebUtility.UrlEncode(credentials.Domain));
-
- EventSourceTrace("Proxy credentials set.");
- SetCurlOption(CURLoption.CURLOPT_PROXYUSERPWD, credentialText);
- }
- }
-
- internal void SetCredentialsOptions(KeyValuePair<NetworkCredential, CURLAUTH> credentialSchemePair)
- {
- if (credentialSchemePair.Key == null)
- {
- EventSourceTrace("Credentials cleared.");
- SetCurlOption(CURLoption.CURLOPT_USERNAME, IntPtr.Zero);
- SetCurlOption(CURLoption.CURLOPT_PASSWORD, IntPtr.Zero);
- return;
- }
-
- NetworkCredential credentials = credentialSchemePair.Key;
- CURLAUTH authScheme = credentialSchemePair.Value;
- string userName = string.IsNullOrEmpty(credentials.Domain) ?
- credentials.UserName :
- credentials.Domain + "\\" + credentials.UserName;
-
- SetCurlOption(CURLoption.CURLOPT_USERNAME, userName);
- SetCurlOption(CURLoption.CURLOPT_HTTPAUTH, (long)authScheme);
- if (credentials.Password != null)
- {
- SetCurlOption(CURLoption.CURLOPT_PASSWORD, credentials.Password);
- }
-
- EventSourceTrace("Credentials set.");
- }
-
- private static KeyValuePair<NetworkCredential, CURLAUTH> GetDefaultCredentialAndAuth() =>
- new KeyValuePair<NetworkCredential, CURLAUTH>(CredentialCache.DefaultNetworkCredentials, CURLAUTH.Negotiate);
-
- internal void SetCookieOption(Uri uri)
- {
- if (!_handler._useCookies)
- {
- return;
- }
-
- string cookieValues = _handler.CookieContainer.GetCookieHeader(uri);
- if (!string.IsNullOrEmpty(cookieValues))
- {
- SetCurlOption(CURLoption.CURLOPT_COOKIE, cookieValues);
- EventSourceTrace<string>("Cookies: {0}", cookieValues);
- }
- }
-
- internal void SetRequestHeaders(bool copyAuthHeaders)
- {
- var slist = new SafeCurlSListHandle();
-
- bool suppressContentType;
- if (_requestMessage.Content != null)
- {
- // Add content request headers
- AddRequestHeaders(_requestMessage.Content.Headers, slist, copyAuthHeaders);
- suppressContentType = _requestMessage.Content.Headers.ContentType == null;
- }
- else
- {
- suppressContentType = true;
- }
-
- if (suppressContentType)
- {
- // Remove the Content-Type header libcurl adds by default.
- ThrowOOMIfFalse(Interop.Http.SListAppend(slist, NoContentType));
- }
-
- // Add request headers
- AddRequestHeaders(_requestMessage.Headers, slist, copyAuthHeaders);
-
- // Since libcurl always adds a Transfer-Encoding header, we need to explicitly block
- // it if caller specifically does not want to set the header
- if (_requestMessage.Headers.TransferEncodingChunked.HasValue &&
- !_requestMessage.Headers.TransferEncodingChunked.Value)
- {
- ThrowOOMIfFalse(Interop.Http.SListAppend(slist, NoTransferEncoding));
- }
-
- // Since libcurl adds an Expect header if it sees enough post data, we need to explicitly block
- // it unless the caller has explicitly opted-in to it.
- if (!_requestMessage.Headers.ExpectContinue.GetValueOrDefault())
- {
- ThrowOOMIfFalse(Interop.Http.SListAppend(slist, NoExpect));
- }
-
- if (!slist.IsInvalid)
- {
- SafeCurlSListHandle prevList = _requestHeaders;
- _requestHeaders = slist;
- SetCurlOption(CURLoption.CURLOPT_HTTPHEADER, slist);
- prevList?.Dispose();
- }
- else
- {
- slist.Dispose();
- }
- }
-
- private void SetSslOptions()
- {
- // SSL Options should be set regardless of the type of the original request,
- // in case an http->https redirection occurs.
- //
- // While this does slow down the theoretical best path of the request the code
- // to decide that we need to register the callback is more complicated than, and
- // potentially more expensive than, just always setting the callback.
- SslProvider.SetSslOptions(this, _handler.ClientCertificateOptions);
- }
-
- internal bool ServerCertificateValidationCallbackAcceptsAll => ReferenceEquals(
- _handler.ServerCertificateCustomValidationCallback,
- HttpClientHandler.DangerousAcceptAnyServerCertificateValidator);
-
- internal void SetCurlCallbacks(
- IntPtr easyGCHandle,
- ReadWriteCallback receiveHeadersCallback,
- ReadWriteCallback sendCallback,
- SeekCallback seekCallback,
- ReadWriteCallback receiveBodyCallback,
- DebugCallback debugCallback)
- {
- if (_callbackHandle == null)
- {
- _callbackHandle = new SafeCallbackHandle();
- }
-
- // Add callback for processing headers
- Interop.Http.RegisterReadWriteCallback(
- _easyHandle,
- ReadWriteFunction.Header,
- receiveHeadersCallback,
- easyGCHandle,
- ref _callbackHandle);
- ThrowOOMIfInvalid(_callbackHandle);
-
- // If we're sending data as part of the request and it wasn't already added as
- // in-memory data, add callbacks for sending request data.
- if (!_inMemoryPostContent && _requestMessage.Content != null)
- {
- Interop.Http.RegisterReadWriteCallback(
- _easyHandle,
- ReadWriteFunction.Read,
- sendCallback,
- easyGCHandle,
- ref _callbackHandle);
- Debug.Assert(!_callbackHandle.IsInvalid, $"Should have been allocated (or failed) when originally adding handlers");
-
- Interop.Http.RegisterSeekCallback(
- _easyHandle,
- seekCallback,
- easyGCHandle,
- ref _callbackHandle);
- Debug.Assert(!_callbackHandle.IsInvalid, $"Should have been allocated (or failed) when originally adding handlers");
- }
-
- // If we're expecting any data in response, add a callback for receiving body data
- if (_requestMessage.Method != HttpMethod.Head)
- {
- Interop.Http.RegisterReadWriteCallback(
- _easyHandle,
- ReadWriteFunction.Write,
- receiveBodyCallback,
- easyGCHandle,
- ref _callbackHandle);
- Debug.Assert(!_callbackHandle.IsInvalid, $"Should have been allocated (or failed) when originally adding handlers");
- }
-
- if (NetEventSource.IsEnabled)
- {
- SetCurlOption(CURLoption.CURLOPT_VERBOSE, 1L);
- CURLcode curlResult = Interop.Http.RegisterDebugCallback(
- _easyHandle,
- debugCallback,
- easyGCHandle,
- ref _callbackHandle);
- Debug.Assert(!_callbackHandle.IsInvalid, $"Should have been allocated (or failed) when originally adding handlers");
- if (curlResult != CURLcode.CURLE_OK)
- {
- EventSourceTrace("Failed to register debug callback.");
- }
- }
- }
-
- internal CURLcode SetSslCtxCallback(SslCtxCallback callback, IntPtr userPointer)
- {
- if (_callbackHandle == null)
- {
- _callbackHandle = new SafeCallbackHandle();
- }
-
- return Interop.Http.RegisterSslCtxCallback(_easyHandle, callback, userPointer, ref _callbackHandle);
- }
-
- private static void AddRequestHeaders(HttpHeaders headers, SafeCurlSListHandle handle, bool copyAuthHeaders)
- {
- foreach (KeyValuePair<string, IEnumerable<string>> header in headers)
- {
- if (string.Equals(header.Key, HttpKnownHeaderNames.ContentLength, StringComparison.OrdinalIgnoreCase) ||
- (!copyAuthHeaders && string.Equals(header.Key, HttpKnownHeaderNames.Authorization, StringComparison.OrdinalIgnoreCase)))
- {
- // avoid overriding libcurl's handling via INFILESIZE/POSTFIELDSIZE
- continue;
- }
-
- string headerKeyAndValue;
- string[] values = header.Value as string[];
- Debug.Assert(values != null, "Implementation detail, but expected Value to be a string[]");
- if (values != null && values.Length < 2)
- {
- // 0 or 1 values
- headerKeyAndValue = values.Length == 0 || string.IsNullOrEmpty(values[0]) ?
- header.Key + ";" : // semicolon used by libcurl to denote empty value that should be sent
- header.Key + ": " + values[0];
- }
- else
- {
- // Either Values wasn't a string[], or it had 2 or more items. Both are handled by GetHeaderString.
- string headerValue = headers.GetHeaderString(header.Key);
- headerKeyAndValue = string.IsNullOrEmpty(headerValue) ?
- header.Key + ";" : // semicolon needed by libcurl; see above
- header.Key + ": " + headerValue;
- }
-
- ThrowOOMIfFalse(Interop.Http.SListAppend(handle, headerKeyAndValue));
- }
- }
-
- internal void SetCurlOption(CURLoption option, string value)
- {
- ThrowIfCURLEError(Interop.Http.EasySetOptionString(_easyHandle, option, value));
- }
-
- internal CURLcode TrySetCurlOption(CURLoption option, string value)
- {
- return Interop.Http.EasySetOptionString(_easyHandle, option, value);
- }
-
- internal void SetCurlOption(CURLoption option, long value)
- {
- ThrowIfCURLEError(Interop.Http.EasySetOptionLong(_easyHandle, option, value));
- }
-
- internal void SetCurlOption(CURLoption option, IntPtr value)
- {
- ThrowIfCURLEError(Interop.Http.EasySetOptionPointer(_easyHandle, option, value));
- }
-
- internal void SetCurlOption(CURLoption option, SafeHandle value)
- {
- ThrowIfCURLEError(Interop.Http.EasySetOptionPointer(_easyHandle, option, value));
- }
-
- private static void ThrowOOMIfFalse(bool appendResult)
- {
- if (!appendResult)
- {
- ThrowOOM();
- }
- }
-
- private static void ThrowOOMIfInvalid(SafeHandle handle)
- {
- if (handle.IsInvalid)
- {
- ThrowOOM();
- }
- }
-
- private static void ThrowOOM()
- {
- throw CreateHttpRequestException(new CurlException((int)CURLcode.CURLE_OUT_OF_MEMORY, isMulti: false));
- }
-
- internal sealed class SendTransferState : IDisposable
- {
- internal byte[] Buffer { get; private set; }
- internal int Offset { get; set; }
- internal int Count { get; set; }
- internal Task<int> Task { get; private set; }
-
- public SendTransferState(int bufferLength)
- {
- Debug.Assert(bufferLength > 0 && bufferLength <= MaxRequestBufferSize, $"Expected 0 < bufferLength <= {MaxRequestBufferSize}, got {bufferLength}");
- Buffer = ArrayPool<byte>.Shared.Rent(bufferLength);
- }
-
- public void Dispose()
- {
- byte[] b = Buffer;
- if (b != null)
- {
- Buffer = null;
- ArrayPool<byte>.Shared.Return(b);
- }
- }
-
- public void SetTaskOffsetCount(Task<int> task, int offset, int count)
- {
- Debug.Assert(offset >= 0, "Offset should never be negative");
- Debug.Assert(count >= 0, "Count should never be negative");
- Debug.Assert(offset <= count, "Offset should never be greater than count");
-
- Task = task;
- Offset = offset;
- Count = count;
- }
- }
-
- internal void StoreLastEffectiveUri()
- {
- IntPtr urlCharPtr; // do not free; will point to libcurl private memory
- CURLcode urlResult = Interop.Http.EasyGetInfoPointer(_easyHandle, Interop.Http.CURLINFO.CURLINFO_EFFECTIVE_URL, out urlCharPtr);
- if (urlResult == CURLcode.CURLE_OK && urlCharPtr != IntPtr.Zero)
- {
- string url = Marshal.PtrToStringAnsi(urlCharPtr);
- if (url != _requestMessage.RequestUri.OriginalString)
- {
- Uri finalUri;
- if (Uri.TryCreate(url, UriKind.Absolute, out finalUri))
- {
- _requestMessage.RequestUri = finalUri;
- }
- }
- return;
- }
-
- Debug.Fail("Expected to be able to get the last effective Uri from libcurl");
- }
-
- private void EventSourceTrace<TArg0>(string formatMessage, TArg0 arg0, [CallerMemberName] string memberName = null)
- {
- CurlHandler.EventSourceTrace(formatMessage, arg0, easy: this, memberName: memberName);
- }
-
- private void EventSourceTrace(string message, [CallerMemberName] string memberName = null)
- {
- CurlHandler.EventSourceTrace(message, easy: this, memberName: memberName);
- }
- }
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Runtime.CompilerServices;
-using System.Runtime.ExceptionServices;
-using System.Runtime.InteropServices;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Win32.SafeHandles;
-
-using CURLcode = Interop.Http.CURLcode;
-using CURLMcode = Interop.Http.CURLMcode;
-using CURLINFO = Interop.Http.CURLINFO;
-
-namespace System.Net.Http
-{
- internal partial class CurlHandler : HttpMessageHandler
- {
- /// <summary>Provides a multi handle and the associated processing for all requests on the handle.</summary>
- private sealed class MultiAgent : IDisposable
- {
- /// <summary>
- /// Amount of time in milliseconds to keep a multiagent worker alive when there's no work to be done.
- /// Increasing this value makes it more likely that a worker will end up getting reused for subsequent
- /// requests, saving on the costs associated with spinning up a new worker, but at the same time it
- /// burns a thread for that period of time.
- /// </summary>
- private const int KeepAliveMilliseconds = 50;
-
- private static readonly Interop.Http.ReadWriteCallback s_receiveHeadersCallback = CurlReceiveHeadersCallback;
- private static readonly Interop.Http.ReadWriteCallback s_sendCallback = CurlSendCallback;
- private static readonly Interop.Http.SeekCallback s_seekCallback = CurlSeekCallback;
- private static readonly Interop.Http.ReadWriteCallback s_receiveBodyCallback = CurlReceiveBodyCallback;
- private static readonly Interop.Http.DebugCallback s_debugCallback = CurlDebugFunction;
-
- /// <summary>CurlHandler that created this MultiAgent. If null, this is a shared handler.</summary>
- private readonly CurlHandler _creatingHandler;
-
- /// <summary>
- /// A collection of not-yet-processed incoming requests for work to be done
- /// by this multi agent. This can include making new requests, canceling
- /// active requests, or unpausing active requests.
- /// Protected by a lock on <see cref="_incomingRequests"/>.
- /// </summary>
- private readonly Queue<IncomingRequest> _incomingRequests = new Queue<IncomingRequest>();
-
- /// <summary>Map of activeOperations, indexed by a GCHandle to a StrongToWeakReference{EasyRequest}.</summary>
- private readonly Dictionary<IntPtr, ActiveRequest> _activeOperations = new Dictionary<IntPtr, ActiveRequest>();
-
- /// <summary>
- /// Special file descriptor used to wake-up curl_multi_wait calls. This is the read
- /// end of a pipe, with the write end written to when work is queued or when cancellation
- /// is requested. This is only valid while the worker is executing.
- /// </summary>
- private SafeFileHandle _wakeupRequestedPipeFd;
-
- /// <summary>
- /// Write end of the pipe connected to <see cref="_wakeupRequestedPipeFd"/>.
- /// This is only valid while the worker is executing.
- /// </summary>
- private SafeFileHandle _requestWakeupPipeFd;
-
- /// <summary>
- /// Task for the currently running worker, or null if there is no current worker.
- /// Protected by a lock on <see cref="_incomingRequests"/>.
- /// </summary>
- private Task _runningWorker;
-
- /// <summary>
- /// Multi handle used to service all requests on this agent. It's lazily
- /// created when it's first needed, so that it can utilize all of the settings
- /// from the associated handler, and it's kept open for the duration of this
- /// agent so that all of the resources it pools (connection pool, DNS cache, etc.)
- /// can be used for all requests on this agent.
- /// </summary>
- private Interop.Http.SafeCurlMultiHandle _multiHandle;
-
- /// <summary>Set when Dispose has been called.</summary>
- private bool _disposed;
-
- /// <summary>Initializes the MultiAgent.</summary>
- /// <param name="handler">The handler that created this agent, or null if it's shared.</param>
- public MultiAgent(CurlHandler handler)
- {
- _creatingHandler = handler;
- }
-
- /// <summary>Disposes of the agent.</summary>
- public void Dispose()
- {
- EventSourceTrace(null);
- _disposed = true;
- QueueIfRunning(new IncomingRequest { Type = IncomingRequestType.Shutdown });
- _multiHandle?.Dispose();
- }
-
- /// <summary>Queues a request for the multi handle to process.</summary>
- public void Queue(IncomingRequest request)
- {
- lock (_incomingRequests)
- {
- // Add the request, then initiate processing.
- _incomingRequests.Enqueue(request);
- EnsureWorkerIsRunning();
- }
- }
-
- /// <summary>Queues a request for the multi handle to process, but only if there's already an active worker running.</summary>
- public void QueueIfRunning(IncomingRequest request)
- {
- lock (_incomingRequests)
- {
- if (_runningWorker != null)
- {
- _incomingRequests.Enqueue(request);
- if (_incomingRequests.Count == 1)
- {
- RequestWakeup();
- }
- }
- }
- }
-
- /// <summary>Gets the ID of the currently running worker, or null if there isn't one.</summary>
- internal int? RunningWorkerId => _runningWorker?.Id;
-
- /// <summary>Schedules the processing worker if one hasn't already been scheduled.</summary>
- private void EnsureWorkerIsRunning()
- {
- Debug.Assert(Monitor.IsEntered(_incomingRequests), "Needs to be called under _incomingRequests lock");
-
- if (_runningWorker == null)
- {
- EventSourceTrace("MultiAgent worker queueing");
-
- // Ensure we've created the multi handle for this agent.
- if (_multiHandle == null)
- {
- _multiHandle = CreateAndConfigureMultiHandle();
- }
-
- // Create pipe used to forcefully wake up curl_multi_wait calls when something important changes.
- // This is created here so that the pipe is available immediately for subsequent queue calls to use.
- Debug.Assert(_wakeupRequestedPipeFd == null, "Read pipe should have been cleared");
- Debug.Assert(_requestWakeupPipeFd == null, "Write pipe should have been cleared");
- unsafe
- {
- int* fds = stackalloc int[2];
- Interop.CheckIo(Interop.Sys.Pipe(fds));
- _wakeupRequestedPipeFd = new SafeFileHandle((IntPtr)fds[Interop.Sys.ReadEndOfPipe], true);
- _requestWakeupPipeFd = new SafeFileHandle((IntPtr)fds[Interop.Sys.WriteEndOfPipe], true);
- }
-
- // Create the processing task. It's "DenyChildAttach" to avoid any surprises if
- // code happens to create attached tasks, and it's LongRunning because this thread
- // is likely going to sit around for a while in a wait loop (and the more requests
- // are concurrently issued to the same agent, the longer the thread will be around).
- _runningWorker = new Task(s => ((MultiAgent)s).WorkerBody(), this,
- CancellationToken.None, TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning);
-
- // We want to avoid situations where a Dispose occurs while we're in the middle
- // of processing requests and causes us to tear out the multi handle while it's
- // in active use. To avoid that, we add-ref it here, and release it at the end
- // of the worker loop.
- bool ignored = false;
- _multiHandle.DangerousAddRef(ref ignored);
-
- // Kick off the processing task. This is done after both setting _runningWorker
- // to non-null and add-refing the handle, both to avoid race conditions. The worker
- // body needs to see _runningWorker as non-null and assumes that it's free to use
- // the multi handle, without fear of it having been disposed.
- _runningWorker.Start(TaskScheduler.Default);
- }
- else // _runningWorker != null
- {
- // The worker is already running. If there are already queued requests, we're done.
- // However, if there aren't any queued requests, Process could be blocked inside of
- // curl_multi_wait, and we want to make sure it wakes up to see that there additional
- // requests waiting to be handled. So we write to the wakeup pipe.
- Debug.Assert(_incomingRequests.Count >= 1, "We just queued a request, so the count should be at least 1");
- if (_incomingRequests.Count == 1)
- {
- RequestWakeup();
- }
- }
- }
-
- /// <summary>Write a byte to the wakeup pipe.</summary>
- private unsafe void RequestWakeup()
- {
- EventSourceTrace(null);
- byte b = 1;
- Interop.CheckIo(Interop.Sys.Write(_requestWakeupPipeFd, &b, 1));
- }
-
- /// <summary>Clears data from the wakeup pipe.</summary>
- /// <remarks>
- /// This must only be called when we know there's data to be read.
- /// The MultiAgent could easily deadlock if it's called when there's no data in the pipe.
- /// </remarks>
- private unsafe void ReadFromWakeupPipeWhenKnownToContainData()
- {
- // It's possible but unlikely that there will be tons of extra data in the pipe,
- // more than we end up reading out here (it's unlikely because we only write a byte to the pipe when
- // transitioning from 0 to 1 incoming request). In that unlikely event, the worst
- // case will be that the next one or more waits will wake up immediately, with each one
- // subsequently clearing out more of the pipe.
- const int ClearBufferSize = 64; // sufficiently large to clear the pipe in any normal case
- byte* clearBuf = stackalloc byte[ClearBufferSize];
- Interop.CheckIo(Interop.Sys.Read(_wakeupRequestedPipeFd, clearBuf, ClearBufferSize));
- }
-
- /// <summary>Requests that libcurl unpause the connection associated with this request.</summary>
- internal void RequestUnpause(EasyRequest easy)
- {
- EventSourceTrace(null, easy: easy);
- QueueIfRunning(new IncomingRequest { Easy = easy, Type = IncomingRequestType.Unpause });
- }
-
- /// <summary>Requests that the request associated with the easy operation be canceled.</summary>
- internal void RequestCancel(EasyRequest easy)
- {
- EventSourceTrace(null, easy: easy);
- QueueIfRunning(new IncomingRequest { Easy = easy, Type = IncomingRequestType.Cancel });
- }
-
- /// <summary>Creates and configures a new multi handle.</summary>
- private Interop.Http.SafeCurlMultiHandle CreateAndConfigureMultiHandle()
- {
- // Create the new handle
- Interop.Http.SafeCurlMultiHandle multiHandle = Interop.Http.MultiCreate();
- if (multiHandle.IsInvalid)
- {
- throw CreateHttpRequestException(new CurlException((int)CURLcode.CURLE_FAILED_INIT, isMulti: false));
- }
-
- // In support of HTTP/2, enable HTTP/2 connections to be multiplexed if possible.
- // We must only do this if the version of libcurl being used supports HTTP/2 multiplexing.
- // Due to a change in a libcurl signature, if we try to make this call on an older libcurl,
- // we'll end up accidentally and unconditionally enabling HTTP 1.1 pipelining.
- if (s_supportsHttp2Multiplexing)
- {
- ThrowIfCURLMError(Interop.Http.MultiSetOptionLong(multiHandle,
- Interop.Http.CURLMoption.CURLMOPT_PIPELINING,
- (long)Interop.Http.CurlPipe.CURLPIPE_MULTIPLEX));
- EventSourceTrace("Set multiplexing on multi handle");
- }
-
- // Configure max connections per host if it was changed from the default. In shared mode,
- // this will be pulled from the handler that first created the agent; the setting from subsequent
- // handlers that use this will be ignored.
- if (_creatingHandler != null)
- {
- int maxConnections = _creatingHandler.MaxConnectionsPerServer;
- if (maxConnections < int.MaxValue) // int.MaxValue considered infinite, mapping to libcurl default of 0
- {
- CURLMcode code = Interop.Http.MultiSetOptionLong(multiHandle, Interop.Http.CURLMoption.CURLMOPT_MAX_HOST_CONNECTIONS, maxConnections);
- switch (code)
- {
- case CURLMcode.CURLM_OK:
- EventSourceTrace("Set max host connections to {0}", maxConnections);
- break;
- default:
- // Treat failures as non-fatal in release; worst case is we employ more connections than desired.
- EventSourceTrace("Setting CURLMOPT_MAX_HOST_CONNECTIONS failed: {0}. Ignoring option.", code);
- break;
- }
- }
- }
-
- return multiHandle;
- }
-
- /// <summary>Thread work item entrypoint for a multiagent worker.</summary>
- private void WorkerBody()
- {
- Debug.Assert(!Monitor.IsEntered(_incomingRequests), $"No locks should be held while invoking {nameof(WorkerBody)}");
- Debug.Assert(_runningWorker != null && _runningWorker.Id == Task.CurrentId, "This is the worker, so it must be running");
-
- EventSourceTrace("MultiAgent worker running");
- try
- {
- try
- {
- // Do the actual processing
- WorkerBodyLoop();
- }
- finally
- {
- EventSourceTrace("MultiAgent worker shutting down");
-
- // The multi handle's reference count was increased prior to launching
- // this processing task. Release that reference; any Dispose operations
- // that occurred during the worker's processing will now be allowed to
- // proceed to clean up the multi handle.
- _multiHandle.DangerousRelease();
-
- lock (_incomingRequests)
- {
- // Close our wakeup pipe (ignore close errors).
- // This is done while holding the lock to prevent
- // subsequent Queue calls to see an improperly configured
- // set of descriptors.
- _wakeupRequestedPipeFd.Dispose();
- _wakeupRequestedPipeFd = null;
- _requestWakeupPipeFd.Dispose();
- _requestWakeupPipeFd = null;
-
- // In the time between we stopped processing and taking the lock,
- // more requests could have been added. If they were,
- // kick off another processing loop.
- _runningWorker = null;
- if (_incomingRequests.Count > 0 && !_disposed)
- {
- EnsureWorkerIsRunning();
- }
- }
- }
- }
- catch (Exception exc)
- {
- // Something went very wrong. In general this should not happen. The only time it might reasonably
- // happen is if CurlHandler is disposed of while it's actively processing, in which case we could
- // get an ObjectDisposedException.
- EventSourceTrace("Unexpected worker failure: {0}", exc);
- Debug.Assert(exc is ObjectDisposedException, $"Unexpected exception from processing loop: {exc}");
-
- // At this point if there any queued requests but there's no worker,
- // those queued requests are potentially going to sit there waiting forever,
- // resulting in a hang. Instead, fail those requests.
- lock (_incomingRequests)
- {
- if (_runningWorker == null)
- {
- while (_incomingRequests.Count > 0)
- {
- _incomingRequests.Dequeue().Easy.CleanupAndFailRequest(exc);
- }
- }
- }
- }
- }
-
- /// <summary>Main processing loop employed by the multiagent worker body.</summary>
- private void WorkerBodyLoop()
- {
- Debug.Assert(_wakeupRequestedPipeFd != null, "Should have a non-null pipe for wake ups");
- Debug.Assert(!_wakeupRequestedPipeFd.IsInvalid, "Should have a valid pipe for wake ups");
- Debug.Assert(!_wakeupRequestedPipeFd.IsClosed, "Should have an open pipe for wake ups");
-
- Debug.Assert(_multiHandle != null, "Should have a non-null multi handle");
- Debug.Assert(!_multiHandle.IsInvalid, "Should have a valid multi handle");
- Debug.Assert(!_multiHandle.IsClosed, "Should have an open multi handle");
-
- // Clear our active operations table. This should already be clear, either because
- // all previous operations completed without unexpected exception, or in the case of an
- // unexpected exception we should have cleaned up gracefully anyway. But just in case...
- Debug.Assert(_activeOperations.Count == 0, "We shouldn't have any active operations when starting processing.");
- _activeOperations.Clear();
-
- Exception eventLoopError = null;
- try
- {
- // Continue processing as long as there are any active operations
- while (true)
- {
- // First handle any requests in the incoming requests queue.
- // (This method is factored out mostly to keep this loop concise, but also partly
- // to avoid keeping any references to EasyRequests rooted by the stack and thus
- // preventing them from being GC'd and the response stream finalized. That's mainly
- // a concern for debug builds, where the JIT may extend a local's lifetime. The same
- // logic applies to some of the other helpers used later in this loop.)
- HandleIncomingRequests();
-
- // If we have no active operations, there's no work to do right now.
- if (_activeOperations.Count == 0)
- {
- // Setting up a MultiAgent processing loop involves creating a new MultiAgent, creating a task
- // and a thread to process it, creating a new pipe, etc., which has non-trivial cost associated
- // with it. Thus, to avoid repeatedly spinning up and down these workers, we can keep this worker
- // alive for a little while longer in case another request comes in within some reasonably small
- // amount of time. This helps with the case where a sequence of requests is made serially rather
- // than in parallel, avoiding the likely case of spinning up a new multiagent for each.
- Interop.Sys.PollEvents triggered;
- Interop.Error pollRv = Interop.Sys.Poll(_wakeupRequestedPipeFd, Interop.Sys.PollEvents.POLLIN, KeepAliveMilliseconds, out triggered);
- if (pollRv == Interop.Error.SUCCESS && (triggered & Interop.Sys.PollEvents.POLLIN) != 0)
- {
- // Another request came in while we were waiting. Clear the pipe and loop around to continue processing.
- ReadFromWakeupPipeWhenKnownToContainData();
- continue;
- }
-
- // We're done. Exit the multiagent.
- return;
- }
-
- // We have one or more active operations. Run any work that needs to be run.
- PerformCurlWork();
-
- // Complete and remove any requests that have finished being processed.
- Interop.Http.CURLMSG message;
- IntPtr easyHandle;
- CURLcode result;
- while (Interop.Http.MultiInfoRead(_multiHandle, out message, out easyHandle, out result))
- {
- HandleCurlMessage(message, easyHandle, result);
- }
-
- // If there are any active operations, wait for more things to do.
- if (_activeOperations.Count > 0)
- {
- WaitForWork();
- }
- }
- }
- catch (Exception exc)
- {
- eventLoopError = exc;
- throw;
- }
- finally
- {
- // There may still be active operations, if an unexpected exception occurred.
- // Make sure to clean up any remaining operations, failing them and releasing their resources.
- if (_activeOperations.Count > 0)
- {
- CleanUpRemainingActiveOperations(eventLoopError);
- }
- }
- }
-
- /// <summary>
- /// Drains the incoming requests queue, dequeuing each request and handling it according to its type.
- /// </summary>
- private void HandleIncomingRequests()
- {
- Debug.Assert(!Monitor.IsEntered(_incomingRequests), "Incoming requests lock should only be held while accessing the queue");
- EventSourceTrace(null);
-
- while (true)
- {
- // Get the next request
- IncomingRequest request;
- lock (_incomingRequests)
- {
- if (_incomingRequests.Count == 0)
- {
- return;
- }
-
- request = _incomingRequests.Dequeue();
- }
-
- // Process the request
- EasyRequest easy = request.Easy;
- EventSourceTrace("Type: {0}", request.Type, easy: easy);
- switch (request.Type)
- {
- case IncomingRequestType.New:
- ActivateNewRequest(easy);
- break;
-
- case IncomingRequestType.Cancel:
- Debug.Assert(easy._associatedMultiAgent == this, "Should only cancel associated easy requests");
- FindFailAndCleanupActiveRequest(easy, new OperationCanceledException(easy._cancellationToken));
- break;
-
- case IncomingRequestType.Unpause:
- Debug.Assert(easy._associatedMultiAgent == this, "Should only unpause associated easy requests");
- if (!easy._easyHandle.IsClosed)
- {
- IntPtr gcHandlePtr;
- ActiveRequest ar;
- Debug.Assert(FindActiveRequest(easy, out gcHandlePtr, out ar), "Couldn't find active request for unpause");
-
- try
- {
- ThrowIfCURLEError(Interop.Http.EasyUnpause(easy._easyHandle));
- }
- catch (Exception exc)
- {
- FindFailAndCleanupActiveRequest(easy, exc);
- }
- }
- break;
-
- case IncomingRequestType.Shutdown:
- // When we get a shutdown request, we want to stop all operations that haven't had
- // their response message published. Other operations may continue.
- Debug.Assert(easy == null, "Expected null easy for a Shutdown request");
- CleanUpRemainingActiveOperations(
- new OperationCanceledException(SR.net_http_unix_handler_disposed),
- onlyIfResponseMessageNotPublished: true);
- break;
-
- default:
- Debug.Fail("Invalid request type: " + request.Type);
- break;
- }
- }
- }
-
- /// <summary>Tell libcurl to perform any available processing on the easy handles associated with this agent's multi handle.</summary>
- private void PerformCurlWork()
- {
- CURLMcode performResult;
- EventSourceTrace("Ask libcurl to perform any available work...");
- while ((performResult = Interop.Http.MultiPerform(_multiHandle)) == CURLMcode.CURLM_CALL_MULTI_PERFORM) ;
- EventSourceTrace("...done performing work: {0}", performResult);
- ThrowIfCURLMError(performResult);
- }
-
- /// <summary>
- /// Tell libcurl to block waiting for work to be ready to handle. It'll return when there's work to be
- /// performed, when a timeout has occurred, or when new requests have entered our incoming requests queue.
- /// </summary>
- private void WaitForWork()
- {
- // Ask libcurl to wait for more things to do. We pass in our wakeup-requested pipe handle so that libcurl
- // will wait on that file descriptor as well and wake up if an incoming request arrived into our queue.
- bool isWakeupRequestedPipeActive;
- bool isTimeout;
- ThrowIfCURLMError(Interop.Http.MultiWait(_multiHandle, _wakeupRequestedPipeFd, out isWakeupRequestedPipeActive, out isTimeout));
-
- if (isWakeupRequestedPipeActive)
- {
- // We woke up (at least in part) because a wake-up was requested.
- // Read the data out of the pipe to clear it.
- Debug.Assert(!isTimeout, $"Should not have timed out when {nameof(isWakeupRequestedPipeActive)} is true");
- EventSourceTrace("Wait wake-up");
- ReadFromWakeupPipeWhenKnownToContainData();
- }
-
- if (isTimeout)
- {
- EventSourceTrace("Wait timeout");
- }
-
- // PERF NOTE: curl_multi_wait uses poll (assuming it's available), which is O(N) in terms of the number of fds
- // being waited on. If this ends up being a scalability bottleneck, we can look into using the curl_multi_socket_*
- // APIs, which would let us switch to using epoll by being notified when sockets file descriptors are added or
- // removed and configuring the epoll context with EPOLL_CTL_ADD/DEL, which at the expense of a lot of additional
- // complexity would let us turn the O(N) operation into an O(1) operation. The additional complexity would come
- // not only in the form of additional callbacks and managing the socket collection, but also in the form of timer
- // management, which is necessary when using the curl_multi_socket_* APIs and which we avoid by using just
- // curl_multi_wait/perform.
- }
-
- /// <summary>Handle a libcurl message received as part of processing work. This should signal a completed operation.</summary>
- private void HandleCurlMessage(Interop.Http.CURLMSG message, IntPtr easyHandle, CURLcode result)
- {
- if (message != Interop.Http.CURLMSG.CURLMSG_DONE)
- {
- Debug.Fail($"CURLMSG_DONE is supposed to be the only message type, but got {message}");
- EventSourceTrace("Unexpected CURLMSG: {0}", message);
- return;
- }
-
- // Get the GCHandle pointer from the easy handle's state
- IntPtr gcHandlePtr;
- CURLcode getInfoResult = Interop.Http.EasyGetInfoPointer(easyHandle, CURLINFO.CURLINFO_PRIVATE, out gcHandlePtr);
- Debug.Assert(getInfoResult == CURLcode.CURLE_OK, $"Failed to get info on a completing easy handle: {getInfoResult}");
- if (getInfoResult == CURLcode.CURLE_OK)
- {
- // Use the GCHandle to look up the associated ActiveRequest
- ActiveRequest completedOperation;
- bool gotActiveOp = _activeOperations.TryGetValue(gcHandlePtr, out completedOperation);
- Debug.Assert(gotActiveOp, "Expected to find GCHandle ptr in active operations table");
-
- // Deactivate the easy handle and finish all processing related to the request
- DeactivateActiveRequest(completedOperation, gcHandlePtr);
- FinishRequest(completedOperation.EasyWrapper, result);
- }
- }
-
- /// <summary>When shutting down the multi agent worker, ensure any active operations are forcibly completed.</summary>
- /// <param name="error">The error to use to complete any remaining operations.</param>
- /// <param name="onlyIfResponseMessageNotPublished">
- /// true if the only active operations that should be canceled and cleaned up are those which have not
- /// yet had their response message published. false if all active operations should be canceled regardless
- /// of where they are in processing.
- /// </param>
- private void CleanUpRemainingActiveOperations(Exception error, bool onlyIfResponseMessageNotPublished = false)
- {
- EventSourceTrace("Shutting down {0} active operations.", _activeOperations.Count);
- try
- {
- // Copy the operations to a tmp array so that we don't try to modify the dictionary while enumerating it
- var activeOps = new KeyValuePair<IntPtr, ActiveRequest>[_activeOperations.Count];
- ((IDictionary<IntPtr, ActiveRequest>)_activeOperations).CopyTo(activeOps, 0);
-
- // Fail all active ops.
- Exception lastError = null;
- foreach (KeyValuePair<IntPtr, ActiveRequest> pair in activeOps)
- {
- try
- {
- IntPtr failingOperationGcHandle = pair.Key;
- ActiveRequest failingActiveRequest = pair.Value;
- EasyRequest easy = failingActiveRequest.EasyWrapper.Target; // may be null if the EasyRequest was already collected
- if (!onlyIfResponseMessageNotPublished || (easy != null && !easy.Task.IsCompleted))
- {
- // Deactivate the request, removing it from the multi handle and allowing it to be cleaned up
- DeactivateActiveRequest(failingActiveRequest, failingOperationGcHandle);
-
- // Complete the operation's task and clean up any of its resources, if it still exists.
- easy?.CleanupAndFailRequest(CreateHttpRequestException(error));
- }
- }
- catch (Exception e)
- {
- // We don't want a spurious failure while cleaning up one request to prevent us from trying
- // to clean up the rest of them.
- lastError = e;
- }
- }
-
- // Now propagate any failure that may have occurred while cleaning up
- if (lastError != null)
- {
- ExceptionDispatchInfo.Throw(lastError);
- }
- }
- finally
- {
- if (!onlyIfResponseMessageNotPublished)
- {
- // Ensure the table is now cleared.
- _activeOperations.Clear();
- }
- }
- }
-
- /// <summary>
- /// Activates the request represented by the EasyRequest. This includes creating the libcurl easy handle,
- /// configuring it, and associating it with the multi handle so that it may be processed.
- /// </summary>
- private void ActivateNewRequest(EasyRequest easy)
- {
- Debug.Assert(easy != null, "We should never get a null request");
- Debug.Assert(easy._associatedMultiAgent == this, "Request should be associated with this agent");
-
- // If cancellation has been requested, complete the request proactively
- if (easy._cancellationToken.IsCancellationRequested)
- {
- easy.CleanupAndFailRequest(new OperationCanceledException(easy._cancellationToken));
- return;
- }
-
- // We need to create a GCHandle that we can pass to libcurl to let it keep associated managed
- // state alive and help us to determine which state corresponds to the particular request. However,
- // having a GCHandle that keeps an EasyRequest alive will prevent finalization of anything to do with
- // that EasyRequest, which means we could end up in a situation where code creates and then drops a
- // request, but then libcurl ends up keeping the state alive (until the reuest/response eventually times
- // out, assuming the timeout wasn't set to infinite). To address this, we create a GCHandle to a wrapper
- // object. At first, that wrapper object wraps a strong reference to the EasyRequest, since until a
- // response comes back, the caller doesn't actually have a reference to anything related to the request.
- // Then once a response comes back and the caller is responsible for keeping the request/response alive,
- // we replace the wrapped state with a weak reference to the EasyRequest. That way, if the user then
- // drops the response, we can allow it to be finalized and not keep it alive indefinitely by the
- // native reference. Finalization of the response stream will cause all of the relevant state to be
- // closed, including closing out the native easy session and then free'ing this GCHandle.
- easy._selfStrongToWeakReference = new StrongToWeakReference<EasyRequest>(easy); // store wrapper onto the easy so that it can transition it to weak and then lose the ref
- GCHandle gcHandle = GCHandle.Alloc(easy._selfStrongToWeakReference);
- IntPtr gcHandlePtr = GCHandle.ToIntPtr(gcHandle);
-
- // Configure the easy request and add it to the multi handle.
- bool addedRef = false;
- try
- {
- easy.InitializeCurl();
-
- easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_PRIVATE, gcHandlePtr);
- easy.SetCurlCallbacks(gcHandlePtr, s_receiveHeadersCallback, s_sendCallback, s_seekCallback, s_receiveBodyCallback, s_debugCallback);
-
- // Make sure that as long as the easy handle is referenced by the multi handle that
- // it doesn't get finalized. Doing so can lead to serious problems like seg faults,
- // for example if the multi handle is trying to access the easy handle on one thread
- // while it's being finalized on another.
- easy._easyHandle.DangerousAddRef(ref addedRef);
-
- // Finally, register the easy handle with the multi handle
- ThrowIfCURLMError(Interop.Http.MultiAddHandle(_multiHandle, easy._easyHandle));
- }
- catch (Exception exc)
- {
- if (addedRef)
- {
- easy._easyHandle.DangerousRelease();
- }
- gcHandle.Free();
- easy.CleanupAndFailRequest(exc);
- return;
- }
-
- // And if cancellation can be requested, hook up a cancellation callback.
- // This callback will put the easy request back into the queue, which will
- // ensure that a wake-up request has been issued.
- var cancellationReg = default(CancellationTokenRegistration);
- if (easy._cancellationToken.CanBeCanceled)
- {
- // To avoid keeping the EasyRequest rooted in the associated CancellationTokenSource,
- // the cancellation registration is given the wrapper rather than the object directly.
- cancellationReg = easy._cancellationToken.Register(s =>
- {
- var wrapper = (StrongToWeakReference<EasyRequest>)s;
- EasyRequest e = wrapper.Target; // may be null if already collected
- e?._associatedMultiAgent.RequestCancel(e);
- }, easy._selfStrongToWeakReference);
- }
-
- // Finally, add it to our map.
- _activeOperations.Add(gcHandlePtr, new ActiveRequest
- {
- EasyWrapper = easy._selfStrongToWeakReference,
- EasyHandle = easy._easyHandle,
- CancellationRegistration = cancellationReg,
- });
- }
-
- /// <summary>Extract the EasyRequest from the GCHandle pointer to it.</summary>
- internal static bool TryGetEasyRequestFromGCHandle(IntPtr gcHandlePtr, out EasyRequest easy)
- {
- // Get the EasyRequest from the context
- try
- {
- GCHandle handle = GCHandle.FromIntPtr(gcHandlePtr);
- easy = (handle.Target as StrongToWeakReference<EasyRequest>)?.Target;
- return easy != null;
- }
- catch (Exception e) when (e is InvalidCastException || e is InvalidOperationException)
- {
- Debug.Fail($"Error accessing GCHandle: {e}");
- }
-
- easy = null;
- return false;
- }
-
- /// <summary>
- /// Corresponding to ActivateNewRequest, removes the active request from the multi handle, frees the GCHandle,
- /// removes the request from our tracking table, and ensures cancellation has been unregistered.
- /// </summary>
- private void DeactivateActiveRequest(ActiveRequest activeRequest, IntPtr gcHandlePtr)
- {
- try
- {
- // Remove the operation from the multi handle so we can shut down the multi handle cleanly
- CURLMcode removeResult = Interop.Http.MultiRemoveHandle(_multiHandle, activeRequest.EasyHandle);
- Debug.Assert(removeResult == CURLMcode.CURLM_OK, "Failed to remove easy handle"); // ignore cleanup errors in release
-
-#if !SYSNETHTTP_NO_OPENSSL
- Interop.Crypto.ErrClearError(); // Ensure that no SSL errors were left on the queue by libcurl.
-#endif
-
- // Release the associated GCHandle so that it's not kept alive forever
- if (gcHandlePtr != IntPtr.Zero)
- {
- try
- {
- GCHandle.FromIntPtr(gcHandlePtr).Free();
- bool removed = _activeOperations.Remove(gcHandlePtr);
- Debug.Assert(removed, "Expected GCHandle to still be referenced by active operations table");
- }
- catch (InvalidOperationException)
- {
- Debug.Fail("Couldn't get/free the GCHandle for an active operation while shutting down due to failure");
- }
- }
-
- // Undo cancellation registration
- activeRequest.CancellationRegistration.Dispose();
- }
- finally
- {
- // We previously AddRef'd the easy handle to ensure that it wasn't finalized
- // while it was still registered with the multi handle. Now that it's been removed,
- // we need to remove the reference.
- activeRequest.EasyHandle.DangerousRelease();
- }
- }
-
- /// <summary>
- /// Looks up an ActiveRequest in the active operations table by EasyRequest. This is a linear operation
- /// and should not be used on hot paths.
- /// </summary>
- private bool FindActiveRequest(EasyRequest easy, out IntPtr gcHandlePtr, out ActiveRequest activeRequest)
- {
- // We maintain an IntPtr=>ActiveRequest mapping, which makes it cheap to look-up by GCHandle ptr but
- // expensive to look up by EasyRequest. If we find this becoming a bottleneck, we can add a reverse
- // map that stores the other direction as well. It should only be used on slow paths, such as when
- // completing an operation due to failure.
- foreach (KeyValuePair<IntPtr, ActiveRequest> pair in _activeOperations)
- {
- if (pair.Value.EasyWrapper.Target == easy)
- {
- gcHandlePtr = pair.Key;
- activeRequest = pair.Value;
- return true;
- }
- }
-
- gcHandlePtr = IntPtr.Zero;
- activeRequest = default(ActiveRequest);
- return false;
- }
-
- /// <summary>
- /// Finds in the active operations table the operation for the specified easy request,
- /// and then assuming it's found, deactivates and fails it with the specified exception.
- /// </summary>
- private void FindFailAndCleanupActiveRequest(EasyRequest easy, Exception error)
- {
- EventSourceTrace("Error: {0}", error, easy: easy);
-
- IntPtr gcHandlePtr;
- ActiveRequest activeRequest;
- if (FindActiveRequest(easy, out gcHandlePtr, out activeRequest))
- {
- DeactivateActiveRequest(activeRequest, gcHandlePtr);
- easy.CleanupAndFailRequest(error);
- }
- else
- {
- Debug.Assert(easy.Task.IsCompleted, "We should only not be able to find the request if it failed or we started to send back the response.");
- }
- }
-
- /// <summary>Finishes the processing of a completed easy operation.</summary>
- private void FinishRequest(StrongToWeakReference<EasyRequest> easyWrapper, CURLcode messageResult)
- {
- EasyRequest completedOperation = easyWrapper.Target;
- EventSourceTrace("Curl result: {0}", messageResult, easy: completedOperation);
-
- if (completedOperation == null)
- {
- // Already collected; nothing more to do.
- return;
- }
-
- if (completedOperation._responseMessage.StatusCode != HttpStatusCode.Unauthorized)
- {
- // If preauthentication is enabled, then we want to transfer credentials to the handler's credential cache.
- // That entails asking the easy operation which auth types are supported, and then giving that info to the
- // handler, which along with the request URI and its server credentials will populate the cache appropriately.
- if (completedOperation._handler.PreAuthenticate)
- {
- long authAvailable;
- if (Interop.Http.EasyGetInfoLong(completedOperation._easyHandle, CURLINFO.CURLINFO_HTTPAUTH_AVAIL, out authAvailable) == CURLcode.CURLE_OK)
- {
- completedOperation._handler.TransferCredentialsToCache(
- completedOperation._requestMessage.RequestUri, (Interop.Http.CURLAUTH)authAvailable);
- }
- // Ignore errors: no need to fail for the sake of putting the credentials into the cache
- }
- }
-
- // Complete or fail the request
- try
- {
- // At this point, we've completed processing the entire request, either due to error
- // or due to completing the entire response.
- completedOperation.Cleanup();
-
- // libcurl will return CURLE_UNSUPPORTED_PROTOCOL if the url it tried to go to had an unsupported protocol.
- // This could be the original url provided or one provided in a Location header for a redirect. Since
- // we vet the original url passed in, such an error here must be for a redirect, in which case we want to
- // ignore it and treat such failures as successes, to match the Windows behavior.
- if (messageResult != CURLcode.CURLE_UNSUPPORTED_PROTOCOL)
- {
- // libcurl will return CURLE_RECV_ERROR (56) if proxy authentication failed when connecting to a https server,
- // whereas it returns CURLE_OK for a http server proxy authentication failure. We ignore this curl behavior error,
- // and let the user rely on response message status code to match the Windows behavior.
- if (messageResult != CURLcode.CURLE_RECV_ERROR ||
- completedOperation._responseMessage.StatusCode != HttpStatusCode.ProxyAuthenticationRequired)
- {
- ThrowIfCURLEError(messageResult);
- }
- }
-
- // Make sure the response message is published, in case it wasn't already, and since we're done processing
- // everything to do with this request, make sure the response stream is marked complete as well.
- completedOperation.EnsureResponseMessagePublished();
- completedOperation._responseMessage.ResponseStream.SignalComplete();
- }
- catch (Exception exc)
- {
- completedOperation.FailRequest(exc);
- }
- }
-
- /// <summary>Callback invoked by libcurl when debug information is available.</summary>
- private static void CurlDebugFunction(IntPtr curl, Interop.Http.CurlInfoType type, IntPtr data, ulong size, IntPtr context)
- {
- EasyRequest easy;
- TryGetEasyRequestFromGCHandle(context, out easy);
- // If we're unable to get an associated request, we simply trace without it.
-
- try
- {
- switch (type)
- {
- case Interop.Http.CurlInfoType.CURLINFO_TEXT:
- case Interop.Http.CurlInfoType.CURLINFO_HEADER_IN:
- case Interop.Http.CurlInfoType.CURLINFO_HEADER_OUT:
- string text = Marshal.PtrToStringAnsi(data, (int)size).Trim();
- if (text.Length > 0)
- {
- CurlHandler.EventSourceTrace("{0}: {1}", type, text, 0, easy: easy);
- }
- break;
-
- default:
- CurlHandler.EventSourceTrace("{0}: {1} bytes", type, size, 0, easy: easy);
- break;
- }
- }
- catch (Exception exc)
- {
- CurlHandler.EventSourceTrace("Error: {0}", exc, easy: easy);
- }
- }
-
- /// <summary>Callback invoked by libcurl for each response header received.</summary>
- private static ulong CurlReceiveHeadersCallback(IntPtr buffer, ulong size, ulong nitems, IntPtr context)
- {
- // The callback is invoked once per header; multi-line headers get merged into a single line.
-
- size *= nitems;
- Debug.Assert(size <= Interop.Http.CURL_MAX_HTTP_HEADER, $"Expected header size <= {Interop.Http.CURL_MAX_HTTP_HEADER}, got {size}");
-
- EasyRequest easy;
- if (TryGetEasyRequestFromGCHandle(context, out easy))
- {
- CurlHandler.EventSourceTrace("Size: {0}", size, easy: easy);
- try
- {
- if (size == 0)
- {
- return 0;
- }
-
- // Make sure we've not yet published the response. This could happen with trailer headers,
- // in which case we just ignore them (we don't want to add them to the response headers at
- // this point, as it'd contribute to a race condition, both in terms of headers appearing
- // "randomly" and in terms of accessing a non-thread-safe data structure from this thread
- // while the consumer might be accessing / mutating it elsewhere.)
- if (easy.Task.IsCompleted)
- {
- CurlHandler.EventSourceTrace("Response already published. Ignoring headers.", easy: easy);
- return size;
- }
-
- CurlResponseMessage response = easy._responseMessage;
- CurlResponseHeaderReader reader = new CurlResponseHeaderReader(buffer, size);
-
- // Validate that we haven't received too much header data.
- // MaxResponseHeadersLength property is in units in K (1024) bytes.
- ulong headerBytesReceived = response._headerBytesReceived + size;
- if (headerBytesReceived > (ulong)(easy._handler.MaxResponseHeadersLength * 1024))
- {
- throw new HttpRequestException(
- SR.Format(SR.net_http_response_headers_exceeded_length, easy._handler.MaxResponseHeadersLength));
- }
- response._headerBytesReceived = (uint)headerBytesReceived;
-
- // Parse the header
- if (reader.ReadStatusLine(response))
- {
- CurlHandler.EventSourceTrace("Received status line", easy: easy);
-
- // Clear the headers when the status line is received. This may happen multiple times if there are multiple response headers (like in redirection).
- response.Headers.Clear();
- response.Content.Headers.Clear();
- response._headerBytesReceived = (uint)size;
-
- // Update the request message with the Uri
- easy.StoreLastEffectiveUri();
- }
- else
- {
- string headerName;
- string headerValue;
-
- if (reader.ReadHeader(out headerName, out headerValue))
- {
- if (!response.Headers.TryAddWithoutValidation(headerName, headerValue))
- {
- response.Content.Headers.TryAddWithoutValidation(headerName, headerValue);
- }
- else if ((int)response.StatusCode >= 300 && (int)response.StatusCode < 400 &&
- easy._handler.AllowAutoRedirect &&
- string.Equals(headerName, HttpKnownHeaderNames.Location, StringComparison.OrdinalIgnoreCase))
- {
- // A "Location" header field can mean different things for different status codes. For 3xx status codes,
- // it implies a redirect. As such, if we got a 3xx status code and we support automatically redirecting,
- // reconfigure the easy handle under the assumption that libcurl will redirect. If it does redirect, we'll
- // be prepared; if it doesn't (e.g. it doesn't treat some particular 3xx as a redirect, if we've reached
- // our redirect limit, etc.), this will have been unnecessary work in reconfiguring the easy handle, but
- // nothing incorrect, as we'll tear down the handle once the request finishes, anyway, and all of the configuration
- // we're doing is about initiating a new request.
- if ((int)response.StatusCode >= 301 && (int)response.StatusCode <= 303)
- {
- // ISSUE: 25163
- // Ideally we want to avoid modifying the users request message.
- easy._requestMessage.Headers.TransferEncodingChunked = false;
- }
- easy.SetPossibleRedirectForLocationHeader(headerValue);
- }
- else if (string.Equals(headerName, HttpKnownHeaderNames.SetCookie, StringComparison.OrdinalIgnoreCase))
- {
- easy._handler.AddResponseCookies(easy, headerValue);
- }
- }
- }
-
- return size;
- }
- catch (Exception ex)
- {
- easy.FailRequest(ex); // cleanup will be handled by main processing loop
- }
- }
-
- // Returning a value other than size fails the callback and forces
- // request completion with an error
- CurlHandler.EventSourceTrace("Aborting request", easy: easy);
- return size - 1;
- }
-
- /// <summary>Callback invoked by libcurl for body data received.</summary>
- private static ulong CurlReceiveBodyCallback(
- IntPtr buffer, ulong size, ulong nitems, IntPtr context)
- {
- size *= nitems;
-
- EasyRequest easy;
- if (TryGetEasyRequestFromGCHandle(context, out easy))
- {
- CurlHandler.EventSourceTrace("Size: {0}", size, easy: easy);
- try
- {
- if (!(easy.Task.IsCanceled || easy.Task.IsFaulted))
- {
- // Complete the task if it hasn't already been. This will make the
- // stream available to consumers. A previous write callback
- // may have already completed the task to publish the response.
- easy.EnsureResponseMessagePublished();
-
- // Try to transfer the data to a reader. This will return either the
- // amount of data transferred (equal to the amount requested
- // to be transferred), or it will return a pause request.
- return easy._responseMessage.ResponseStream.TransferDataToResponseStream(buffer, (long)size);
- }
- }
- catch (Exception ex)
- {
- easy.FailRequest(ex); // cleanup will be handled by main processing loop
- }
- }
-
- // Returning a value other than size fails the callback and forces
- // request completion with an error.
- CurlHandler.EventSourceTrace("Aborting request", easy: easy);
- return (size > 0) ? size - 1 : 1;
- }
-
- /// <summary>Callback invoked by libcurl to read request data.</summary>
- private static ulong CurlSendCallback(IntPtr buffer, ulong size, ulong nitems, IntPtr context)
- {
- int length = checked((int)(size * nitems));
-
- if (TryGetEasyRequestFromGCHandle(context, out EasyRequest easy))
- {
- CurlHandler.EventSourceTrace("Size: {0}", length, easy: easy);
-
- if (length == 0)
- {
- return 0;
- }
-
- Debug.Assert(easy._requestMessage.Content != null, "We should only be in the send callback if we have request content");
- Debug.Assert(easy._associatedMultiAgent != null, "The request should be associated with a multi agent.");
-
- try
- {
- // Transfer data from the request's content stream to libcurl
- return TransferDataFromRequestStream(buffer, length, easy);
- }
- catch (Exception ex)
- {
- easy.FailRequest(ex); // cleanup will be handled by main processing loop
- }
- }
-
- // Something went wrong.
- CurlHandler.EventSourceTrace("Aborting request", easy: easy);
- return Interop.Http.CURL_READFUNC_ABORT;
- }
-
- /// <summary>
- /// Transfers up to <paramref name="length"/> data from the <paramref name="easy"/>'s
- /// request content (non-memory) stream to the buffer.
- /// </summary>
- /// <returns>The number of bytes transferred.</returns>
- private static ulong TransferDataFromRequestStream(IntPtr buffer, int length, EasyRequest easy)
- {
- CurlHandler.EventSourceTrace("Length: {0}", length, easy: easy);
-
- MultiAgent multi = easy._associatedMultiAgent;
-
- // First check to see whether there's any data available from a previous asynchronous read request.
- // If there is, the transfer state's Task field will be non-null, with its Result representing
- // the number of bytes read. The Buffer will then contain all of that read data. If the Count
- // is 0, then this is the first time we're checking that Task, and so we populate the Count
- // from that read result. After that, we can transfer as much data remains between Offset and
- // Count. Multiple callbacks may pull from that one read.
-
- EasyRequest.SendTransferState sts = easy._sendTransferState;
- if (sts != null)
- {
- // Is there a previous read that may still have data to be consumed?
- if (sts.Task != null)
- {
- if (!sts.Task.IsCompleted)
- {
- // We have a previous read that's not yet completed. This should be quite rare, but it can
- // happen when we're unpaused prematurely, potentially due to the request still finishing
- // being sent as the server starts to send a response. Since we still have the outstanding
- // read, we simply re-pause. When the task completes (which could have happened immediately
- // after the check). the continuation we previously created will fire and queue an unpause.
- // Since all of this processing is single-threaded on the current thread, that unpause request
- // is guaranteed to happen after this re-pause.
- multi.EventSourceTrace("Re-pausing reading after a spurious un-pause", easy: easy);
- return Interop.Http.CURL_READFUNC_PAUSE;
- }
-
- // Determine how many bytes were read on the last asynchronous read.
- // If nothing was read, then we're done and can simply return 0 to indicate
- // the end of the stream.
- int bytesRead = sts.Task.GetAwaiter().GetResult(); // will throw if read failed
- Debug.Assert(bytesRead >= 0 && bytesRead <= sts.Buffer.Length, $"ReadAsync returned an invalid result length: {bytesRead}");
- if (bytesRead == 0)
- {
- sts.SetTaskOffsetCount(null, 0, 0);
- return 0;
- }
-
- // If Count is still 0, then this is the first time after the task completed
- // that we're examining the data: transfer the bytesRead to the Count.
- if (sts.Count == 0)
- {
- multi.EventSourceTrace("ReadAsync completed with bytes: {0}", bytesRead, easy: easy);
- sts.Count = bytesRead;
- }
-
- // Now Offset and Count are both accurate. Determine how much data we can copy to libcurl...
- int availableData = sts.Count - sts.Offset;
- Debug.Assert(availableData > 0, "There must be some data still available.");
-
- // ... and copy as much of that as libcurl will allow.
- int bytesToCopy = Math.Min(availableData, length);
- Marshal.Copy(sts.Buffer, sts.Offset, buffer, bytesToCopy);
- multi.EventSourceTrace("Copied {0} bytes from request stream", bytesToCopy, easy: easy);
-
- // Update the offset. If we've gone through all of the data, reset the state
- // so that the next time we're called back we'll do a new read.
- sts.Offset += bytesToCopy;
- Debug.Assert(sts.Offset <= sts.Count, "Offset should never exceed count");
- if (sts.Offset == sts.Count)
- {
- sts.SetTaskOffsetCount(null, 0, 0);
- }
-
- // Return the amount of data copied
- Debug.Assert(bytesToCopy > 0, "We should never return 0 bytes here.");
- return (ulong)bytesToCopy;
- }
-
- // sts was non-null but sts.Task was null, meaning there was no previous task/data
- // from which to satisfy any of this request.
- }
- else // sts == null
- {
- // Allocate a transfer state object to use for the remainder of this request.
- Debug.Assert(easy._requestMessage.Content != null, "Content shouldn't be null, since we already got a content request stream");
- long bufferSize = easy._requestMessage.Content.Headers.ContentLength.GetValueOrDefault();
- if (bufferSize <= 0 || bufferSize > MaxRequestBufferSize)
- {
- bufferSize = MaxRequestBufferSize;
- }
- easy._sendTransferState = sts = new EasyRequest.SendTransferState((int)bufferSize);
- }
-
- Debug.Assert(sts != null, "By this point we should have a transfer object");
- Debug.Assert(sts.Task == null, "There shouldn't be a task now.");
- Debug.Assert(sts.Count == 0, "Count should be zero.");
- Debug.Assert(sts.Offset == 0, "Offset should be zero.");
-
- // If we get here, there was no previously read data available to copy.
-
- // Make sure we actually have a stream to read from. This will be null if either
- // this is the first time we're reading it, or if the stream was reset as part
- // of curl trying to rewind. Then do the read.
- ValueTask<int> asyncRead;
- if (easy._requestContentStream == null)
- {
- multi.EventSourceTrace("Calling ReadAsStreamAsync to get new request stream", easy: easy);
- Task<Stream> readAsStreamTask = easy._requestMessage.Content.ReadAsStreamAsync();
- asyncRead = readAsStreamTask.IsCompleted ?
- StoreRetrievedContentStreamAndReadAsync(readAsStreamTask, easy, sts, length) :
- new ValueTask<int>(easy._requestMessage.Content.ReadAsStreamAsync().ContinueWith((t, s) =>
- {
- var stateAndRequest = (Tuple<int, EasyRequest.SendTransferState, EasyRequest>)s;
- return StoreRetrievedContentStreamAndReadAsync(t,
- stateAndRequest.Item3, stateAndRequest.Item2, stateAndRequest.Item1).AsTask();
- }, Tuple.Create(length, sts, easy), CancellationToken.None,
- TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap());
- }
- else
- {
- multi.EventSourceTrace("Starting async read", easy: easy);
- asyncRead = easy._requestContentStream.ReadAsync(
- new Memory<byte>(sts.Buffer, 0, Math.Min(sts.Buffer.Length, length)), easy._cancellationToken);
- }
-
- // Even though it's "Async", it's possible this read could complete synchronously or extremely quickly.
- // Check to see if it did, in which case we can also satisfy the libcurl request synchronously in this callback.
- if (asyncRead.IsCompleted)
- {
- multi.EventSourceTrace("Async read completed immediately", easy: easy);
-
- // Get the amount of data read.
- int bytesRead = asyncRead.GetAwaiter().GetResult(); // will throw if read failed
- if (bytesRead == 0)
- {
- multi.EventSourceTrace("Read 0 bytes", easy: easy);
- return 0;
- }
-
- // Copy as much as we can.
- int bytesToCopy = Math.Min(bytesRead, length);
- Debug.Assert(bytesToCopy > 0 && bytesToCopy <= sts.Buffer.Length, $"ReadAsync quickly returned an invalid result length: {bytesToCopy}");
- Marshal.Copy(sts.Buffer, 0, buffer, bytesToCopy);
- multi.EventSourceTrace("Read {0} bytes", bytesToCopy, easy: easy);
-
- // If we read more than we were able to copy, stash it away for the next read.
- if (bytesToCopy < bytesRead)
- {
- multi.EventSourceTrace("Storing {0} bytes for later", bytesRead - bytesToCopy, easy: easy);
- sts.SetTaskOffsetCount(asyncRead.AsTask(), bytesToCopy, bytesRead);
- }
-
- // Return the number of bytes read.
- return (ulong)bytesToCopy;
- }
-
- // Otherwise, the read completed asynchronously. Store the task, and hook up a continuation
- // such that the connection will be unpaused once the task completes.
- sts.SetTaskOffsetCount(asyncRead.AsTask(), 0, 0);
- sts.Task.ContinueWith((t, s) =>
- {
- EasyRequest easyRef = (EasyRequest)s;
- easyRef._associatedMultiAgent.RequestUnpause(easyRef);
- }, easy, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
-
- // Then pause the connection.
- multi.EventSourceTrace("Pausing transfer from request stream", easy: easy);
- return Interop.Http.CURL_READFUNC_PAUSE;
- }
-
- /// <summary>
- /// Given a completed task used to retrieve the content stream asynchronously, extracts the stream,
- /// stores it into <see cref="EasyRequest._requestContentStream"/>, and does an initial read on it.
- /// </summary>
- private static ValueTask<int> StoreRetrievedContentStreamAndReadAsync(
- Task<Stream> readAsStreamTask, EasyRequest easy, EasyRequest.SendTransferState sts, int length)
- {
- Debug.Assert(readAsStreamTask.IsCompleted, $"Expected {nameof(readAsStreamTask)} to be completed, got {readAsStreamTask.Status}");
- try
- {
- MultiAgent multi = easy._associatedMultiAgent;
- multi.EventSourceTrace("Async operation completed: {0}", readAsStreamTask.Status, easy: easy);
-
- // Get and store the resulting stream
- easy._requestContentStream = readAsStreamTask.GetAwaiter().GetResult();
- multi.EventSourceTrace("Got stream: {0}", easy._requestContentStream.GetType(), easy: easy);
-
- // If the stream is seekable, store its original position. We'll use this any time we need to seek
- // back to the "beginning", as it's possible the stream isn't at position 0.
- if (easy._requestContentStream.CanSeek)
- {
- long startingPos = easy._requestContentStream.Position;
- easy._requestContentStreamStartingPosition = startingPos;
- CurlHandler.EventSourceTrace("Stream starting position: {0}", startingPos, easy: easy);
- }
-
- // Now that we have a stream, do the desired read
- multi.EventSourceTrace("Starting async read", easy: easy);
- return easy._requestContentStream.ReadAsync(new Memory<byte>(sts.Buffer, 0, Math.Min(sts.Buffer.Length, length)), easy._cancellationToken);
- }
- catch (OperationCanceledException oce)
- {
- return new ValueTask<int>(oce.CancellationToken.IsCancellationRequested ?
- Task.FromCanceled<int>(oce.CancellationToken) :
- Task.FromCanceled<int>(new CancellationToken(true)));
- }
- catch (Exception exc)
- {
- return new ValueTask<int>(Task.FromException<int>(exc));
- }
- }
-
- /// <summary>Callback invoked by libcurl to seek to a position within the request stream.</summary>
- private static Interop.Http.CurlSeekResult CurlSeekCallback(IntPtr context, long offset, int origin)
- {
- EasyRequest easy;
- if (TryGetEasyRequestFromGCHandle(context, out easy))
- {
- CurlHandler.EventSourceTrace("Offset: {0}, Origin: {1}", offset, origin, 0, easy: easy);
- try
- {
- // If we don't have a stream yet, we can't seek.
- if (easy._requestContentStream == null)
- {
- CurlHandler.EventSourceTrace("No request stream exists yet. Can't seek", easy: easy);
- return Interop.Http.CurlSeekResult.CURL_SEEKFUNC_CANTSEEK;
- }
-
- // If the stream is seekable, which is a very common case, everyone is happy.
- // Simply seek on the stream.
- if (easy._requestContentStream.CanSeek)
- {
- CurlHandler.EventSourceTrace("Seeking on the existing stream", easy: easy);
- SeekOrigin seek = (SeekOrigin)origin;
- if (seek == SeekOrigin.Begin)
- {
- Debug.Assert(easy._requestContentStreamStartingPosition.HasValue);
- easy._requestContentStream.Position = easy._requestContentStreamStartingPosition.GetValueOrDefault();
- }
- else
- {
- easy._requestContentStream.Seek(offset, seek);
- }
- return Interop.Http.CurlSeekResult.CURL_SEEKFUNC_OK;
- }
-
- // The stream isn't seekable. Now we start getting into shakier ground.
- // Most of the time the seek callback is used, it's because libcurl is rewinding
- // to the beginning of the stream due to a redirect, an auth challenge, etc. (other
- // cases where it might try to seek elsewhere would be, e.g., with a Range header).
- // In such cases, we can't seek, but we can simply re-read the stream from the content.
- // In most cases this will "just work." There are corner cases, however, where it'll
- // fail but we won't yet know it failed, e.g. if a StreamContent is used, ReadAsStreamAsync
- // will give us back a wrapper stream over the same original underlying stream and without
- // having changed its position (it's not seekable). At that point we'll think
- // we have a new stream, but when reading starts happening, it'll be at the existing
- // position, and we'll only end up sending part of the data (or none in the common case
- // where we'd already read ot the end). As a workaround for that, we can at least special case
- // the StreamContent type, for which we know this will be an issue. It won't help with other
- // corner -case contents like this, but for such contents, we would still end up failing the
- // request, just sooner.
- if (offset == 0 && origin == (int)SeekOrigin.Begin &&
- !(easy._requestMessage.Content is StreamContent)) // avoid known problematic case
- {
- CurlHandler.EventSourceTrace("Removing the existing request stream, to be replaced on subsequent read", easy: easy);
- easy._requestContentStream = null;
- }
-
- // Can't seek. Let libcurl know: it may still be able to recover.
- CurlHandler.EventSourceTrace("Can't seek", easy: easy);
- return Interop.Http.CurlSeekResult.CURL_SEEKFUNC_CANTSEEK;
- }
- catch (Exception ex)
- {
- easy.FailRequest(ex); // cleanup will be handled by main processing loop
- }
- }
-
- // Something went wrong
- CurlHandler.EventSourceTrace("Seek failed", easy: easy);
- return Interop.Http.CurlSeekResult.CURL_SEEKFUNC_FAIL;
- }
-
- private void EventSourceTrace<TArg0>(string formatMessage, TArg0 arg0, EasyRequest easy = null, [CallerMemberName] string memberName = null)
- {
- CurlHandler.EventSourceTrace(formatMessage, arg0, this, easy, memberName);
- }
-
- private void EventSourceTrace(string message, EasyRequest easy = null, [CallerMemberName] string memberName = null)
- {
- CurlHandler.EventSourceTrace(message, this, easy, memberName);
- }
-
- /// <summary>Represents an active request currently being processed by the agent.</summary>
- private struct ActiveRequest
- {
- public StrongToWeakReference<EasyRequest> EasyWrapper;
- public Interop.Http.SafeCurlHandle EasyHandle;
- public CancellationTokenRegistration CancellationRegistration;
- }
-
- /// <summary>Represents an incoming request to be processed by the agent.</summary>
- internal struct IncomingRequest
- {
- public IncomingRequestType Type;
- public EasyRequest Easy;
- }
-
- /// <summary>The type of an incoming request to be processed by the agent.</summary>
- internal enum IncomingRequestType : byte
- {
- /// <summary>A new request that's never been submitted to an agent.</summary>
- New,
- /// <summary>A request to cancel a request previously submitted to the agent.</summary>
- Cancel,
- /// <summary>A request to unpause the connection associated with a request previously submitted to the agent.</summary>
- Unpause,
- /// <summary>A request to shutdown the agent and all active operations. No easy request is associated with this type.</summary>
- Shutdown
- }
- }
-
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Diagnostics;
-using System.IO;
-using System.Net.Security;
-using System.Runtime.InteropServices;
-using System.Security.Authentication;
-using System.Security.Cryptography;
-using System.Security.Cryptography.X509Certificates;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Win32.SafeHandles;
-
-using CURLcode = Interop.Http.CURLcode;
-using CURLINFO = Interop.Http.CURLINFO;
-
-namespace System.Net.Http
-{
- internal partial class CurlHandler : HttpMessageHandler
- {
- private static class SslProvider
- {
- private static readonly Interop.Http.SslCtxCallback s_sslCtxCallback = SslCtxCallback;
- private static readonly Interop.Ssl.AppVerifyCallback s_sslVerifyCallback = VerifyCertChain;
- private static readonly Oid s_serverAuthOid = new Oid("1.3.6.1.5.5.7.3.1");
- private static string _sslCaPath;
- private static string _sslCaInfo;
-
- internal static void SetSslOptions(EasyRequest easy, ClientCertificateOption clientCertOption)
- {
- EventSourceTrace("ClientCertificateOption: {0}", clientCertOption, easy:easy);
- Debug.Assert(clientCertOption == ClientCertificateOption.Automatic || clientCertOption == ClientCertificateOption.Manual);
-
- // Create a client certificate provider if client certs may be used.
- X509Certificate2Collection clientCertificates = easy._handler._clientCertificates;
- ClientCertificateProvider certProvider =
- clientCertOption == ClientCertificateOption.Automatic ? new ClientCertificateProvider(null) : // automatic
- clientCertificates?.Count > 0 ? new ClientCertificateProvider(clientCertificates) : // manual with certs
- null; // manual without certs
- IntPtr userPointer = IntPtr.Zero;
- if (certProvider != null)
- {
- EventSourceTrace("Created certificate provider", easy:easy);
-
- // The client cert provider needs to be passed through to the callback, and thus
- // we create a GCHandle to keep it rooted. This handle needs to be cleaned up
- // when the request has completed, and a simple and pay-for-play way to do that
- // is by cleaning it up in a continuation off of the request.
- userPointer = GCHandle.ToIntPtr(certProvider._gcHandle);
- easy.Task.ContinueWith((_, state) => ((IDisposable)state).Dispose(), certProvider,
- CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
- }
-
- // Configure the options. Our best support is when targeting OpenSSL/1.0. For other backends,
- // we fall back to a minimal amount of support, and may throw a PNSE based on the options requested.
- if (Interop.Http.HasMatchingOpenSslVersion)
- {
- // Register the callback with libcurl. We need to register even if there's no user-provided
- // server callback and even if there are no client certificates, because we support verifying
- // server certificates against more than those known to OpenSSL.
- SetSslOptionsForSupportedBackend(easy, certProvider, userPointer);
- }
- else
- {
- // Newer versions of OpenSSL, and other non-OpenSSL backends, do not currently support callbacks.
- // That means we'll throw a PNSE if a callback is required.
- SetSslOptionsForUnsupportedBackend(easy, certProvider);
- }
- }
-
- private static void SetSslOptionsForSupportedBackend(EasyRequest easy, ClientCertificateProvider certProvider, IntPtr userPointer)
- {
- CURLcode answer = easy.SetSslCtxCallback(s_sslCtxCallback, userPointer);
- EventSourceTrace("Callback registration result: {0}", answer, easy: easy);
- switch (answer)
- {
- case CURLcode.CURLE_OK:
- // We successfully registered. If we'll be invoking a user-provided callback to verify the server
- // certificate as part of that, disable libcurl's verification of the host name; we need to get
- // the callback from libcurl even if the host name doesn't match, so we take on the responsibility
- // of doing the host name match in the callback prior to invoking the user's delegate.
- if (easy._handler.ServerCertificateCustomValidationCallback != null)
- {
- easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSL_VERIFYHOST, 0);
- // But don't change the CURLOPT_SSL_VERIFYPEER setting, as setting it to 0 will
- // cause SSL and libcurl to ignore the result of the server callback.
- }
-
- SetSslOptionsForCertificateStore(easy);
-
- // The allowed SSL protocols will be set in the configuration callback.
- break;
-
- case CURLcode.CURLE_UNKNOWN_OPTION: // Curl 7.38 and prior
- case CURLcode.CURLE_NOT_BUILT_IN: // Curl 7.39 and later
- SetSslOptionsForUnsupportedBackend(easy, certProvider);
- break;
-
- default:
- ThrowIfCURLEError(answer);
- break;
- }
- }
-
- private static void GetSslCaLocations(out string path, out string info)
- {
- // We only provide curl option values when SSL_CERT_FILE or SSL_CERT_DIR is set.
- // When that is the case, we set the options so curl ends up using the same certificates as the
- // X509 machine store.
- path = _sslCaPath;
- info = _sslCaInfo;
-
- if (path == null || info == null)
- {
- bool hasEnvironmentVariables = Environment.GetEnvironmentVariable("SSL_CERT_FILE") != null ||
- Environment.GetEnvironmentVariable("SSL_CERT_DIR") != null;
-
- if (hasEnvironmentVariables)
- {
- path = Interop.Crypto.GetX509RootStorePath();
- if (!Directory.Exists(path))
- {
- // X509 store ignores non-existing.
- path = string.Empty;
- }
-
- info = Interop.Crypto.GetX509RootStoreFile();
- if (!File.Exists(info))
- {
- // X509 store ignores non-existing.
- info = string.Empty;
- }
- }
- else
- {
- path = string.Empty;
- info = string.Empty;
- }
- _sslCaPath = path;
- _sslCaInfo = info;
- }
- }
-
- private static void SetSslOptionsForCertificateStore(EasyRequest easy)
- {
- // Support specifying certificate directory/bundle via environment variables: SSL_CERT_DIR, SSL_CERT_FILE.
- GetSslCaLocations(out string sslCaPath, out string sslCaInfo);
-
- if (sslCaPath != string.Empty)
- {
- easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_CAPATH, sslCaPath);
-
- // https proxy support requires libcurl 7.52.0+
- easy.TrySetCurlOption(Interop.Http.CURLoption.CURLOPT_PROXY_CAPATH, sslCaPath);
- }
-
- if (sslCaInfo != string.Empty)
- {
- easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_CAINFO, sslCaInfo);
-
- // https proxy support requires libcurl 7.52.0+
- easy.TrySetCurlOption(Interop.Http.CURLoption.CURLOPT_PROXY_CAINFO, sslCaInfo);
- }
- }
-
- private static void SetSslOptionsForUnsupportedBackend(EasyRequest easy, ClientCertificateProvider certProvider)
- {
- if (certProvider != null)
- {
- throw new PlatformNotSupportedException(SR.Format(SR.net_http_libcurl_clientcerts_notsupported_sslbackend, CurlVersionDescription, CurlSslVersionDescription, Interop.Http.RequiredOpenSslDescription));
- }
-
- if (easy._handler.CheckCertificateRevocationList)
- {
- throw new PlatformNotSupportedException(SR.Format(SR.net_http_libcurl_revocation_notsupported_sslbackend, CurlVersionDescription, CurlSslVersionDescription, Interop.Http.RequiredOpenSslDescription));
- }
-
- if (easy._handler.ServerCertificateCustomValidationCallback != null)
- {
- if (easy.ServerCertificateValidationCallbackAcceptsAll)
- {
- EventSourceTrace("Warning: Disabling peer and host verification per {0}", nameof(HttpClientHandler.DangerousAcceptAnyServerCertificateValidator), easy: easy);
- easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSL_VERIFYPEER, 0);
- easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSL_VERIFYHOST, 0);
- }
- else
- {
- throw new PlatformNotSupportedException(SR.Format(SR.net_http_libcurl_callback_notsupported_sslbackend, CurlVersionDescription, CurlSslVersionDescription, Interop.Http.RequiredOpenSslDescription));
- }
- }
- else
- {
- SetSslOptionsForCertificateStore(easy);
- }
-
- // In case of defaults configure the allowed SSL protocols.
- SetSslVersion(easy);
- }
-
- private static void SetSslVersion(EasyRequest easy, IntPtr sslCtx = default(IntPtr))
- {
- // Get the requested protocols.
- SslProtocols protocols = easy._handler.SslProtocols;
- if (protocols == SslProtocols.None)
- {
- // Let libcurl use its defaults if None is set.
- return;
- }
-
- // libcurl supports options for either enabling all of the TLS1.* protocols or enabling
- // just one protocol; it doesn't currently support enabling two of the three, e.g. you can't
- // pick TLS1.1 and TLS1.2 but not TLS1.0, but you can select just TLS1.2.
- Interop.Http.CurlSslVersion curlSslVersion;
- switch (protocols)
- {
-#pragma warning disable 0618 // SSL2/3 are deprecated
- case SslProtocols.Ssl2:
- curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_SSLv2;
- break;
- case SslProtocols.Ssl3:
- curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_SSLv3;
- break;
-#pragma warning restore 0618
-
- case SslProtocols.Tls:
- curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_0;
- break;
- case SslProtocols.Tls11:
- curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_1;
- break;
- case SslProtocols.Tls12:
- curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_2;
- break;
- case SslProtocols.Tls13:
- curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_3;
- break;
-
- case SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12:
- case SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13:
- curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1;
- break;
-
- default:
- throw new NotSupportedException(SR.net_securityprotocolnotsupported);
- }
-
- try
- {
- easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSLVERSION, (long)curlSslVersion);
- }
- catch (CurlException e) when (e.HResult == (int)CURLcode.CURLE_UNKNOWN_OPTION)
- {
- throw new NotSupportedException(SR.net_securityprotocolnotsupported, e);
- }
- }
-
- private static CURLcode SslCtxCallback(IntPtr curl, IntPtr sslCtx, IntPtr userPointer)
- {
- EasyRequest easy;
- if (!TryGetEasyRequest(curl, out easy))
- {
- return CURLcode.CURLE_ABORTED_BY_CALLBACK;
- }
- EventSourceTrace(null, easy: easy);
-
- // Configure the SSL protocols allowed.
- SslProtocols protocols = easy._handler.SslProtocols;
- if (protocols == SslProtocols.None)
- {
- // If None is selected, let OpenSSL use its defaults, but with SSL2/3 explicitly disabled.
- // Since the shim/OpenSSL work on a disabling system, where any protocols for which bits aren't
- // set are disabled, we set all of the bits other than those we want disabled.
-#pragma warning disable 0618 // the enum values are obsolete
- protocols = ~(SslProtocols.Ssl2 | SslProtocols.Ssl3);
-#pragma warning restore 0618
- }
- Interop.Ssl.SetProtocolOptions(sslCtx, protocols);
-
- // Configure the SSL server certificate verification callback.
- Interop.Ssl.SslCtxSetCertVerifyCallback(sslCtx, s_sslVerifyCallback, curl);
-
- // If a client certificate provider was provided, also configure the client certificate callback.
- if (userPointer != IntPtr.Zero)
- {
- try
- {
- // Provider is passed in via a GCHandle. Get the provider, which contains
- // the client certificate callback delegate.
- GCHandle handle = GCHandle.FromIntPtr(userPointer);
- ClientCertificateProvider provider = (ClientCertificateProvider)handle.Target;
- if (provider == null)
- {
- Debug.Fail($"Expected non-null provider in {nameof(SslCtxCallback)}");
- return CURLcode.CURLE_ABORTED_BY_CALLBACK;
- }
-
- // Register the callback.
- Interop.Ssl.SslCtxSetClientCertCallback(sslCtx, provider._callback);
- EventSourceTrace("Registered client certificate callback.", easy: easy);
- }
- catch (Exception e)
- {
- Debug.Fail($"Exception in {nameof(SslCtxCallback)}", e.ToString());
- return CURLcode.CURLE_ABORTED_BY_CALLBACK;
- }
- }
-
- return CURLcode.CURLE_OK;
- }
-
- private static bool TryGetEasyRequest(IntPtr curlPtr, out EasyRequest easy)
- {
- Debug.Assert(curlPtr != IntPtr.Zero, "curlPtr is not null");
-
- IntPtr gcHandlePtr;
- CURLcode getInfoResult = Interop.Http.EasyGetInfoPointer(curlPtr, CURLINFO.CURLINFO_PRIVATE, out gcHandlePtr);
- if (getInfoResult == CURLcode.CURLE_OK)
- {
- return MultiAgent.TryGetEasyRequestFromGCHandle(gcHandlePtr, out easy);
- }
-
- Debug.Fail($"Failed to get info on a completing easy handle: {getInfoResult}");
- easy = null;
- return false;
- }
-
- private static int VerifyCertChain(IntPtr storeCtxPtr, IntPtr curlPtr)
- {
- const int SuccessResult = 1, FailureResult = 0;
-
- EasyRequest easy;
- if (!TryGetEasyRequest(curlPtr, out easy))
- {
- EventSourceTrace("Could not find associated easy request: {0}", curlPtr);
- return FailureResult;
- }
-
- var storeCtx = new SafeX509StoreCtxHandle(storeCtxPtr, ownsHandle: false);
- try
- {
- return VerifyCertChain(storeCtx, easy) ? SuccessResult : FailureResult;
- }
- catch (Exception exc)
- {
- EventSourceTrace("Unexpected exception: {0}", exc, easy: easy);
- easy.FailRequest(CreateHttpRequestException(new CurlException((int)CURLcode.CURLE_ABORTED_BY_CALLBACK, exc)));
- return FailureResult;
- }
- finally
- {
- storeCtx.Dispose();
- }
- }
-
- private static bool VerifyCertChain(SafeX509StoreCtxHandle storeCtx, EasyRequest easy)
- {
- IntPtr leafCertPtr = Interop.Crypto.X509StoreCtxGetTargetCert(storeCtx);
- if (leafCertPtr == IntPtr.Zero)
- {
- EventSourceTrace("Invalid certificate pointer", easy: easy);
- return false;
- }
-
- X509Certificate2[] otherCerts = null;
- int otherCertsCount = 0;
- var leafCert = new X509Certificate2(leafCertPtr);
- try
- {
- // We need to respect the user's server validation callback if there is one. If there isn't one,
- // we can start by first trying to use OpenSSL's verification, though only if CRL checking is disabled,
- // as OpenSSL doesn't do that.
- if (easy._handler.ServerCertificateCustomValidationCallback == null &&
- !easy._handler.CheckCertificateRevocationList)
- {
- // Start by using the default verification provided directly by OpenSSL.
- // If it succeeds in verifying the cert chain, we're done. Employing this instead of
- // our custom implementation will need to be revisited if we ever decide to introduce a
- // "disallowed" store that enables users to "untrust" certs the system trusts.
- if (Interop.Crypto.X509VerifyCert(storeCtx))
- {
- return true;
- }
- }
-
- // Either OpenSSL verification failed, or there was a server validation callback
- // or certificate revocation checking was enabled. Either way, fall back to manual
- // and more expensive verification that includes checking the user's certs (not
- // just the system store ones as OpenSSL does).
- using (var chain = new X509Chain())
- {
- chain.ChainPolicy.RevocationMode = easy._handler.CheckCertificateRevocationList ? X509RevocationMode.Online : X509RevocationMode.NoCheck;
- chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
-
- using (SafeSharedX509StackHandle extraStack = Interop.Crypto.X509StoreCtxGetSharedUntrusted(storeCtx))
- {
- if (extraStack.IsInvalid)
- {
- otherCerts = Array.Empty<X509Certificate2>();
- }
- else
- {
- int extraSize = Interop.Crypto.GetX509StackFieldCount(extraStack);
- otherCerts = new X509Certificate2[extraSize];
-
- for (int i = 0; i < extraSize; i++)
- {
- IntPtr certPtr = Interop.Crypto.GetX509StackField(extraStack, i);
- if (certPtr != IntPtr.Zero)
- {
- X509Certificate2 cert = new X509Certificate2(certPtr);
- otherCerts[otherCertsCount++] = cert;
- chain.ChainPolicy.ExtraStore.Add(cert);
- }
- }
- }
- }
-
- var serverCallback = easy._handler._serverCertificateValidationCallback;
- if (serverCallback == null)
- {
- SslPolicyErrors errors = CertificateValidation.BuildChainAndVerifyProperties(chain, leafCert,
- checkCertName: false, hostName: null); // libcurl already verifies the host name
- return errors == SslPolicyErrors.None;
- }
- else
- {
- // Authenticate the remote party: (e.g. when operating in client mode, authenticate the server).
- chain.ChainPolicy.ApplicationPolicy.Add(s_serverAuthOid);
-
- SslPolicyErrors errors = CertificateValidation.BuildChainAndVerifyProperties(chain, leafCert,
- checkCertName: true, hostName: easy._requestMessage.RequestUri.Host); // we disabled automatic host verification, so we do it here
- return serverCallback(easy._requestMessage, leafCert, chain, errors);
- }
- }
- }
- finally
- {
- for (int i = 0; i < otherCertsCount; i++)
- {
- otherCerts[i].Dispose();
- }
- leafCert.Dispose();
- }
- }
- }
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Diagnostics;
-using System.Security.Authentication;
-using System.Security.Cryptography.X509Certificates;
-
-using CURLcode = Interop.Http.CURLcode;
-
-namespace System.Net.Http
-{
- internal partial class CurlHandler : HttpMessageHandler
- {
- private static class SslProvider
- {
- internal static void SetSslOptions(EasyRequest easy, ClientCertificateOption clientCertOption)
- {
- Debug.Assert(
- clientCertOption == ClientCertificateOption.Automatic ||
- clientCertOption == ClientCertificateOption.Manual);
-
- // Create a client certificate provider if client certs may be used.
- X509Certificate2Collection clientCertificates = easy._handler._clientCertificates;
-
- if (clientCertOption != ClientCertificateOption.Manual || clientCertificates?.Count > 0)
- {
- // libcurl does not have an option of accepting a SecIdentityRef via an input option,
- // only via writing it to a file and letting it load the PFX.
- // This would require that a) we write said file, and b) that it contaminate the default
- // keychain (because their PFX loader loads to the default keychain).
- throw new PlatformNotSupportedException(SR.net_http_libcurl_clientcerts_notsupported_os);
- }
-
- // Revocation checking is always on for darwinssl (SecureTransport).
- // If any other backend is used and revocation is requested, we can't guarantee
- // that assertion.
- if (easy._handler.CheckCertificateRevocationList &&
- !CurlSslVersionDescription.Equals(Interop.Http.SecureTransportDescription))
- {
- throw new PlatformNotSupportedException(
- SR.Format(
- SR.net_http_libcurl_revocation_notsupported_sslbackend,
- CurlVersionDescription,
- CurlSslVersionDescription,
- Interop.Http.SecureTransportDescription));
- }
-
- if (easy._handler.ServerCertificateCustomValidationCallback != null)
- {
- // libcurl (as of 7.49.1) does not have any callback which can be registered which fires
- // between the time that a TLS/SSL handshake has offered up the server certificate and the
- // time that the HTTP request headers are written. Were there any callback, the option
- // CURLINFO_TLS_SSL_PTR could be queried (and the backend identifier validated to be
- // CURLSSLBACKEND_DARWINSSL). Then the SecTrustRef could be extracted to build the chain,
- // a la SslStream.
- //
- // Without the callback the matrix looks like:
- // * If default-trusted and callback-would-trust: No difference (except side effects, like logging).
- // * If default-trusted and callback-would-block: Data would have been sent in violation of user trust.
- // * If not-default-trusted and callback-would-not-trust: No difference (except side effects).
- // * If not-default-trusted and callback-would-trust: No data sent, which doesn't match user desires.
- //
- // Of the two "different" cases, sending when we shouldn't is worse, so that's the direction we
- // have to cater to. So we'll use default trust, and throw on any custom callback.
- //
- // The situation where system trust fails can be remedied by including the certificate into the
- // user's keychain and setting the SSL policy trust for it to "Always Trust".
- // Similarly, the "block this" could be attained by setting the SSL policy for a cert in the
- // keychain to "Never Trust".
- //
- // However, one case we can support is when we know all certificates will pass validation.
- // We can detect a key case of that: whether DangerousAcceptAnyServerCertificateValidator was used.
- if (easy.ServerCertificateValidationCallbackAcceptsAll)
- {
- EventSourceTrace("Warning: Disabling peer verification per {0}", nameof(HttpClientHandler.DangerousAcceptAnyServerCertificateValidator), easy: easy);
- easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSL_VERIFYPEER, 0); // don't verify the peer
-
- // Don't set CURLOPT_SSL_VERIFHOST to 0; doing so disables SNI with SecureTransport backend.
- if (!CurlSslVersionDescription.Equals(Interop.Http.SecureTransportDescription))
- {
- easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSL_VERIFYHOST, 0); // don't verify the hostname
- }
- }
- else
- {
- throw new PlatformNotSupportedException(SR.net_http_libcurl_callback_notsupported_os);
- }
- }
-
- SetSslVersion(easy);
- }
-
- private static void SetSslVersion(EasyRequest easy)
- {
- // Get the requested protocols.
- SslProtocols protocols = easy._handler.SslProtocols;
- if (protocols == SslProtocols.None)
- {
- // Let libcurl use its defaults if None is set.
- return;
- }
-
- // libcurl supports options for either enabling all of the TLS1.* protocols or enabling
- // just one protocol; it doesn't currently support enabling two of the three, e.g. you can't
- // pick TLS1.1 and TLS1.2 but not TLS1.0, but you can select just TLS1.2.
- Interop.Http.CurlSslVersion curlSslVersion;
- switch (protocols)
- {
-#pragma warning disable 0618 // SSL2/3 are deprecated
- case SslProtocols.Ssl2:
- curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_SSLv2;
- break;
- case SslProtocols.Ssl3:
- curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_SSLv3;
- break;
-#pragma warning restore 0618
-
- case SslProtocols.Tls:
- curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_0;
- break;
- case SslProtocols.Tls11:
- curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_1;
- break;
- case SslProtocols.Tls12:
- curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_2;
- break;
- case SslProtocols.Tls13:
- curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_3;
- break;
-
- case SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12:
- case SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13:
- curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1;
- break;
-
- default:
- throw new NotSupportedException(SR.net_securityprotocolnotsupported);
- }
-
- try
- {
- easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSLVERSION, (long)curlSslVersion);
- }
- catch (CurlException e) when (e.HResult == (int)CURLcode.CURLE_UNKNOWN_OPTION)
- {
- throw new NotSupportedException(SR.net_securityprotocolnotsupported, e);
- }
- }
- }
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Net.Security;
-using System.Runtime.CompilerServices;
-using System.Security.Authentication;
-using System.Security.Cryptography.X509Certificates;
-using System.Threading;
-using System.Threading.Tasks;
-
-using CURLAUTH = Interop.Http.CURLAUTH;
-using CURLcode = Interop.Http.CURLcode;
-using CURLMcode = Interop.Http.CURLMcode;
-using CURLoption = Interop.Http.CURLoption;
-
-namespace System.Net.Http
-{
- // Object model:
- // -------------
- // CurlHandler provides an HttpMessageHandler implementation that wraps libcurl. The core processing for CurlHandler
- // is handled via a CurlHandler.MultiAgent instance, where currently a CurlHandler instance stores and uses a single
- // MultiAgent for the lifetime of the handler (with the MultiAgent lazily initialized on first use, so that it can
- // be initialized with all of the configured options on the handler). The MultiAgent is named as such because it wraps
- // a libcurl multi handle that's responsible for handling all requests on the instance. When a request arrives, it's
- // queued to the MultiAgent, which ensures that a thread is running to continually loop and process all work associated
- // with the multi handle until no more work is required; at that point, the thread is retired until more work arrives,
- // at which point another thread will be spun up. Any number of requests will have their handling multiplexed onto
- // this one event loop thread. Each request is represented by a CurlHandler.EasyRequest, so named because it wraps
- // a libcurl easy handle, libcurl's representation of a request. The EasyRequest stores all state associated with
- // the request, including the CurlHandler.CurlResponseMessage and CurlHandler.CurlResponseStream that are handed
- // back to the caller to provide access to the HTTP response information.
- //
- // Lifetime:
- // ---------
- // The MultiAgent is initialized on first use and is kept referenced by the CurlHandler for the remainder of the
- // handler's lifetime. Both are disposable, and disposing of the CurlHandler will dispose of the MultiAgent.
- // However, libcurl is not thread safe in that two threads can't be using the same multi or easy handles concurrently,
- // so any interaction with the multi handle must happen on the MultiAgent's thread. For this reason, the
- // SafeHandle storing the underlying multi handle has its ref count incremented when the MultiAgent worker is running
- // and decremented when it stops running, enabling any disposal requests to be delayed until the worker has quiesced.
- // To enable that to happen quickly when a dispose operation occurs, an "incoming request" (how all other threads
- // communicate with the MultiAgent worker) is queued to the worker to request a shutdown; upon receiving that request,
- // the worker will exit and allow the multi handle to be disposed of.
- //
- // An EasyRequest itself doesn't govern its own lifetime. Since an easy handle is added to a multi handle for
- // the multi handle to process, the easy handle must not be destroyed until after it's been removed from the multi handle.
- // As such, once the SafeHandle for an easy handle is created, although its stored in the EasyRequest instance,
- // it's also stored into a dictionary on the MultiAgent and has its ref count incremented to prevent it from being
- // disposed of while it's in use by the multi handle.
- //
- // When a request is made to the CurlHandler, callbacks are registered with libcurl, including state that will
- // be passed back into managed code and used to identify the associated EasyRequest. This means that the native
- // code needs to be able both to keep the EasyRequest alive and to refer to it using an IntPtr. For this, we
- // use a GCHandle to the EasyRequest. However, the native code needs to be able to refer to the EasyRequest for the
- // lifetime of the request, but we also need to avoid keeping the EasyRequest (and all state it references) alive artificially.
- // For the beginning phase of the request, the native code may be the only thing referencing the managed objects, since
- // when a caller invokes "Task<HttpResponseMessage> SendAsync(...)", there's nothing handed back to the caller that represents
- // the request until at least the HTTP response headers are received and the returned Task is completed with the response
- // message object. However, after that point, if the caller drops the HttpResponseMessage, we also want to cancel and
- // dispose of the associated state, which means something needs to be finalizable and not kept rooted while at the same
- // time still allowing the native code to continue using its GCHandle and lookup the associated state as long as it's alive.
- // Yet then when an async read is made on the response message, we want to postpone such finalization and ensure that the async
- // read can be appropriately completed with control and reference ownership given back to the reader. As such, we do two things:
- // we make the response stream finalizable, and we make the GCHandle be to a wrapper object for the EasyRequest rather than to
- // the EasyRequest itself. That wrapper object maintains a weak reference to the EasyRequest as well as sometimes maintaining
- // a strong reference. When the request starts out, the GCHandle is created to the wrapper, which has a strong reference to
- // the EasyRequest (which also back references to the wrapper so that the wrapper can be accessed via it). The GCHandle is
- // thus keeping the EasyRequest and all of the state it references alive, e.g. the CurlResponseStream, which itself has a reference
- // back to the EasyRequest. Once the request progresses to the point of receiving HTTP response headers and the HttpResponseMessage
- // is handed back to the caller, the wrapper object drops its strong reference and maintains only a weak reference. At this
- // point, if the caller were to drop its HttpResponseMessage object, that would also drop the only strong reference to the
- // CurlResponseStream; the CurlResponseStream would be available for collection and finalization, and its finalization would
- // request cancellation of the easy request to the multi agent. The multi agent would then in response remove the easy handle
- // from the multi handle and decrement the ref count on the SafeHandle for the easy handle, allowing it to be finalized and
- // the underlying easy handle released. If instead of dropping the HttpResponseMessage the caller makes a read request on the
- // response stream, the wrapper object is transitioned back to having a strong reference, so that even if the caller then drops
- // the HttpResponseMessage, the read Task returned from the read operation will still be completed eventually, at which point
- // the wrapper will transition back to being a weak reference.
- //
- // Even with that, of course, Dispose is still the recommended way of cleaning things up. Disposing the CurlResponseMessage
- // will Dispose the CurlResponseStream, which will Dispose of the SafeHandle for the easy handle and request that the MultiAgent
- // cancel the operation. Once canceled and removed, the SafeHandle will have its ref count decremented and the previous disposal
- // will proceed to release the underlying handle.
-
- internal partial class CurlHandler : HttpMessageHandler
- {
- #region Constants
-
- private const string UriSchemeHttp = "http";
- private const string UriSchemeHttps = "https";
- private const string EncodingNameGzip = "gzip";
- private const string EncodingNameDeflate = "deflate";
-
- private const int MaxRequestBufferSize = 16384; // Default used by libcurl
- private const string NoTransferEncoding = HttpKnownHeaderNames.TransferEncoding + ":";
- private const string NoContentType = HttpKnownHeaderNames.ContentType + ":";
- private const string NoExpect = HttpKnownHeaderNames.Expect + ":";
-
- #endregion
-
- #region Fields
-
- private static readonly KeyValuePair<string, CURLAUTH>[] s_orderedAuthTypes = new KeyValuePair<string, CURLAUTH>[] {
- new KeyValuePair<string, CURLAUTH>("Negotiate", CURLAUTH.Negotiate),
- new KeyValuePair<string, CURLAUTH>("NTLM", CURLAUTH.NTLM),
- new KeyValuePair<string, CURLAUTH>("Digest", CURLAUTH.Digest),
- new KeyValuePair<string, CURLAUTH>("Basic", CURLAUTH.Basic),
- };
-
- private static readonly bool s_supportsAutomaticDecompression;
- private static readonly bool s_supportsSSL;
- private static readonly bool s_supportsHttp2Multiplexing;
- private static string s_curlVersionDescription;
- private static string s_curlSslVersionDescription;
-
- private static readonly MultiAgent s_singletonSharedAgent;
- private readonly MultiAgent _agent;
- private volatile bool _anyOperationStarted;
- private volatile bool _disposed;
-
- private IWebProxy _proxy = null;
- private ICredentials _serverCredentials = null;
- private bool _useProxy = HttpHandlerDefaults.DefaultUseProxy;
- private ICredentials _defaultProxyCredentials = null;
- private DecompressionMethods _automaticDecompression = HttpHandlerDefaults.DefaultAutomaticDecompression;
- private bool _preAuthenticate = HttpHandlerDefaults.DefaultPreAuthenticate;
- private CredentialCache _credentialCache = null; // protected by LockObject
- private bool _useDefaultCredentials = HttpHandlerDefaults.DefaultUseDefaultCredentials;
- private CookieContainer _cookieContainer = new CookieContainer();
- private bool _useCookies = HttpHandlerDefaults.DefaultUseCookies;
- private bool _automaticRedirection = HttpHandlerDefaults.DefaultAutomaticRedirection;
- private int _maxAutomaticRedirections = HttpHandlerDefaults.DefaultMaxAutomaticRedirections;
- private int _maxConnectionsPerServer = HttpHandlerDefaults.DefaultMaxConnectionsPerServer;
- private int _maxResponseHeadersLength = HttpHandlerDefaults.DefaultMaxResponseHeadersLength;
- private ClientCertificateOption _clientCertificateOption = HttpHandlerDefaults.DefaultClientCertificateOption;
- private X509Certificate2Collection _clientCertificates;
- private Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> _serverCertificateValidationCallback;
- private bool _checkCertificateRevocationList = HttpHandlerDefaults.DefaultCheckCertificateRevocationList;
- private SslProtocols _sslProtocols = SslProtocols.None; // use default
- private IDictionary<string, object> _properties; // Only create dictionary when required.
-
- private object LockObject { get { return _agent; } }
-
- #endregion
-
-#pragma warning disable CA1810 // explicit static cctor
- static CurlHandler()
- {
- // curl_global_init call handled by Interop.LibCurl's cctor
-
- Interop.Http.CurlFeatures features = Interop.Http.GetSupportedFeatures();
- s_supportsSSL = (features & Interop.Http.CurlFeatures.CURL_VERSION_SSL) != 0;
- s_supportsAutomaticDecompression = (features & Interop.Http.CurlFeatures.CURL_VERSION_LIBZ) != 0;
- s_supportsHttp2Multiplexing = (features & Interop.Http.CurlFeatures.CURL_VERSION_HTTP2) != 0 && Interop.Http.GetSupportsHttp2Multiplexing() && !UseSingletonMultiAgent;
-
- if (NetEventSource.IsEnabled)
- {
- EventSourceTrace($"libcurl: {CurlVersionDescription} {CurlSslVersionDescription} {features}");
- }
-
- // By default every CurlHandler gets its own MultiAgent. But for some backends,
- // we need to restrict the number of threads involved in processing libcurl work,
- // so we create a single MultiAgent that's used by all handlers.
- if (UseSingletonMultiAgent)
- {
- s_singletonSharedAgent = new MultiAgent(null);
- }
- }
-#pragma warning restore CA1810
-
- public CurlHandler()
- {
- // If the shared MultiAgent was initialized, use it.
- // Otherwise, create a new MultiAgent for this handler.
- _agent = s_singletonSharedAgent ?? new MultiAgent(this);
- }
-
- #region Properties
-
- private static string CurlVersionDescription => s_curlVersionDescription ?? (s_curlVersionDescription = Interop.Http.GetVersionDescription() ?? string.Empty);
- private static string CurlSslVersionDescription => s_curlSslVersionDescription ?? (s_curlSslVersionDescription = Interop.Http.GetSslVersionDescription() ?? string.Empty);
-
- private static bool UseSingletonMultiAgent
- {
- get
- {
- // Some backends other than OpenSSL need locks initialized in order to use them in a
- // multithreaded context, which would happen with multiple HttpClients and thus multiple
- // MultiAgents. Since we don't currently have the ability to do so initialization, instead we
- // restrict all HttpClients to use the same MultiAgent instance in this case. We know LibreSSL
- // is in this camp, so we currently special-case it.
- string curlSslVersion = Interop.Http.GetSslVersionDescription();
- return
- !string.IsNullOrEmpty(curlSslVersion) &&
- curlSslVersion.StartsWith(Interop.Http.LibreSslDescription, StringComparison.OrdinalIgnoreCase);
- }
- }
-
- internal bool AllowAutoRedirect
- {
- get { return _automaticRedirection; }
- set
- {
- CheckDisposedOrStarted();
- _automaticRedirection = value;
- }
- }
-
- internal bool UseProxy
- {
- get { return _useProxy; }
- set
- {
- CheckDisposedOrStarted();
- _useProxy = value;
- }
- }
-
- internal IWebProxy Proxy
- {
- get { return _proxy; }
- set
- {
- CheckDisposedOrStarted();
- _proxy = value;
- }
- }
-
- internal ICredentials DefaultProxyCredentials
- {
- get { return _defaultProxyCredentials; }
- set
- {
- CheckDisposedOrStarted();
- _defaultProxyCredentials = value;
- }
- }
-
- internal ICredentials Credentials
- {
- get { return _serverCredentials; }
- set { _serverCredentials = value; }
- }
-
- internal ClientCertificateOption ClientCertificateOptions
- {
- get { return _clientCertificateOption; }
- set
- {
- if (value != ClientCertificateOption.Manual &&
- value != ClientCertificateOption.Automatic)
- {
- throw new ArgumentOutOfRangeException(nameof(value));
- }
-
- CheckDisposedOrStarted();
- _clientCertificateOption = value;
- }
- }
-
- internal X509Certificate2Collection ClientCertificates
- {
- get
- {
- if (_clientCertificateOption != ClientCertificateOption.Manual)
- {
- throw new InvalidOperationException(SR.Format(SR.net_http_invalid_enable_first, nameof(ClientCertificateOptions), nameof(ClientCertificateOption.Manual)));
- }
-
- return _clientCertificates ?? (_clientCertificates = new X509Certificate2Collection());
- }
- }
-
- internal Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> ServerCertificateCustomValidationCallback
- {
- get { return _serverCertificateValidationCallback; }
- set
- {
- CheckDisposedOrStarted();
- _serverCertificateValidationCallback = value;
- }
- }
-
- internal bool CheckCertificateRevocationList
- {
- get { return _checkCertificateRevocationList; }
- set
- {
- CheckDisposedOrStarted();
- _checkCertificateRevocationList = value;
- }
- }
-
- internal SslProtocols SslProtocols
- {
- get { return _sslProtocols; }
- set
- {
- CheckDisposedOrStarted();
- _sslProtocols = value;
- }
- }
-
- internal bool SupportsAutomaticDecompression => s_supportsAutomaticDecompression;
-
- internal DecompressionMethods AutomaticDecompression
- {
- get { return _automaticDecompression; }
- set
- {
- CheckDisposedOrStarted();
- _automaticDecompression = value;
- }
- }
-
- internal bool PreAuthenticate
- {
- get { return _preAuthenticate; }
- set
- {
- CheckDisposedOrStarted();
- _preAuthenticate = value;
- if (value && _credentialCache == null)
- {
- _credentialCache = new CredentialCache();
- }
- }
- }
-
- internal bool UseCookies
- {
- get { return _useCookies; }
- set
- {
- CheckDisposedOrStarted();
- _useCookies = value;
- }
- }
-
- internal CookieContainer CookieContainer
- {
- get { return _cookieContainer; }
- set
- {
- CheckDisposedOrStarted();
- _cookieContainer = value;
- }
- }
-
- internal int MaxAutomaticRedirections
- {
- get { return _maxAutomaticRedirections; }
- set
- {
- if (value <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(value), value, SR.Format(SR.net_http_value_must_be_greater_than, 0));
- }
-
- CheckDisposedOrStarted();
- _maxAutomaticRedirections = value;
- }
- }
-
- internal int MaxConnectionsPerServer
- {
- get { return _maxConnectionsPerServer; }
- set
- {
- if (value < 1)
- {
- throw new ArgumentOutOfRangeException(nameof(value), value, SR.Format(SR.net_http_value_must_be_greater_than, 0));
- }
-
- CheckDisposedOrStarted();
- _maxConnectionsPerServer = value;
- }
- }
-
- internal int MaxResponseHeadersLength
- {
- get { return _maxResponseHeadersLength; }
- set
- {
- if (value <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(value), value, SR.Format(SR.net_http_value_must_be_greater_than, 0));
- }
-
- CheckDisposedOrStarted();
- _maxResponseHeadersLength = value;
- }
- }
-
- internal bool UseDefaultCredentials
- {
- get { return _useDefaultCredentials; }
- set
- {
- CheckDisposedOrStarted();
- _useDefaultCredentials = value;
- }
- }
-
- public IDictionary<string, object> Properties
- {
- get
- {
- if (_properties == null)
- {
- _properties = new Dictionary<string, object>();
- }
-
- return _properties;
- }
- }
- #endregion
-
- protected override void Dispose(bool disposing)
- {
- _disposed = true;
- if (disposing && _agent != s_singletonSharedAgent)
- {
- _agent.Dispose();
- }
- base.Dispose(disposing);
- }
-
- protected internal override Task<HttpResponseMessage> SendAsync(
- HttpRequestMessage request, CancellationToken cancellationToken)
- {
- if (request == null)
- {
- throw new ArgumentNullException(nameof(request), SR.net_http_handler_norequest);
- }
-
- if (request.RequestUri.Scheme == UriSchemeHttps)
- {
- if (!s_supportsSSL)
- {
- throw new PlatformNotSupportedException(SR.Format(SR.net_http_unix_https_support_unavailable_libcurl, CurlVersionDescription));
- }
- }
- else
- {
- Debug.Assert(request.RequestUri.Scheme == UriSchemeHttp, "HttpClient expected to validate scheme as http or https.");
- }
-
- if (request.Headers.TransferEncodingChunked.GetValueOrDefault() && (request.Content == null))
- {
- return Task.FromException<HttpResponseMessage>(
- new HttpRequestException(SR.net_http_client_execution_error,
- new InvalidOperationException(SR.net_http_chunked_not_allowed_with_empty_content)));
- }
-
- if (_useCookies && _cookieContainer == null)
- {
- throw new InvalidOperationException(SR.net_http_invalid_cookiecontainer);
- }
-
- CheckDisposed();
- SetOperationStarted();
-
- // Do an initial cancellation check to avoid initiating the async operation if
- // cancellation has already been requested. After this, we'll rely on CancellationToken.Register
- // to notify us of cancellation requests and shut down the operation if possible.
- if (cancellationToken.IsCancellationRequested)
- {
- return Task.FromCanceled<HttpResponseMessage>(cancellationToken);
- }
-
- // Create the easy request. This associates the easy request with this handler and configures
- // it based on the settings configured for the handler.
- var easy = new EasyRequest(this, _agent, request, cancellationToken);
- try
- {
- EventSourceTrace("{0}", request, easy: easy);
- _agent.Queue(new MultiAgent.IncomingRequest { Easy = easy, Type = MultiAgent.IncomingRequestType.New });
- }
- catch (Exception exc)
- {
- easy.CleanupAndFailRequest(exc);
- }
-
- return easy.Task;
- }
-
- #region Private methods
-
- private void SetOperationStarted()
- {
- if (!_anyOperationStarted)
- {
- _anyOperationStarted = true;
- }
- }
-
- private KeyValuePair<NetworkCredential, CURLAUTH> GetCredentials(Uri requestUri)
- {
- // If preauthentication is enabled, we may have populated our internal credential cache,
- // so first check there to see if we have any credentials for this uri.
- if (_preAuthenticate)
- {
- KeyValuePair<NetworkCredential, CURLAUTH> ncAndScheme;
- lock (LockObject)
- {
- Debug.Assert(_credentialCache != null, "Expected non-null credential cache");
- ncAndScheme = GetCredentials(requestUri, _credentialCache, s_orderedAuthTypes);
- }
- if (ncAndScheme.Key != null)
- {
- return ncAndScheme;
- }
- }
-
- // We either weren't preauthenticating or we didn't have any cached credentials
- // available, so check the credentials on the handler.
- return GetCredentials(requestUri, _serverCredentials, s_orderedAuthTypes);
- }
-
- private void TransferCredentialsToCache(Uri serverUri, CURLAUTH serverAuthAvail)
- {
- if (_serverCredentials == null)
- {
- // No credentials, nothing to put into the cache.
- return;
- }
-
- lock (LockObject)
- {
- // For each auth type we allow, check whether it's one supported by the server.
- KeyValuePair<string, CURLAUTH>[] validAuthTypes = s_orderedAuthTypes;
- for (int i = 0; i < validAuthTypes.Length; i++)
- {
- // Is it supported by the server?
- if ((serverAuthAvail & validAuthTypes[i].Value) != 0)
- {
- // And do we have a credential for it?
- NetworkCredential nc = _serverCredentials.GetCredential(serverUri, validAuthTypes[i].Key);
- if (nc != null)
- {
- // We have a credential for it, so add it, and we're done.
- Debug.Assert(_credentialCache != null, "Expected non-null credential cache");
- try
- {
- _credentialCache.Add(serverUri, validAuthTypes[i].Key, nc);
- }
- catch (ArgumentException)
- {
- // Ignore the case of key already present
- }
- break;
- }
- }
- }
- }
- }
-
- private void AddResponseCookies(EasyRequest state, string cookieHeader)
- {
- if (!_useCookies)
- {
- return;
- }
-
- try
- {
- _cookieContainer.SetCookies(state._requestMessage.RequestUri, cookieHeader);
- state.SetCookieOption(state._requestMessage.RequestUri);
- }
- catch (CookieException e)
- {
- EventSourceTrace(
- "Malformed cookie parsing failed: {0}, server: {1}, cookie: {2}",
- e.Message, state._requestMessage.RequestUri, cookieHeader,
- easy: state);
- }
- }
-
- private static KeyValuePair<NetworkCredential, CURLAUTH> GetCredentials(Uri requestUri, ICredentials credentials, KeyValuePair<string, CURLAUTH>[] validAuthTypes)
- {
- NetworkCredential nc = null;
- CURLAUTH curlAuthScheme = CURLAUTH.None;
-
- if (credentials != null)
- {
- // For each auth type we consider valid, try to get a credential for it.
- // Union together the auth types for which we could get credentials, but validate
- // that the found credentials are all the same, as libcurl doesn't support differentiating
- // by auth type.
- for (int i = 0; i < validAuthTypes.Length; i++)
- {
- NetworkCredential networkCredential = credentials.GetCredential(requestUri, validAuthTypes[i].Key);
- if (networkCredential != null)
- {
- curlAuthScheme |= validAuthTypes[i].Value;
- if (nc == null)
- {
- nc = networkCredential;
- }
- else if (!AreEqualNetworkCredentials(nc, networkCredential))
- {
- throw new PlatformNotSupportedException(SR.Format(SR.net_http_unix_invalid_credential, CurlVersionDescription));
- }
- }
- }
- }
-
- EventSourceTrace("Authentication scheme: {0}", curlAuthScheme);
- return new KeyValuePair<NetworkCredential, CURLAUTH>(nc, curlAuthScheme); ;
- }
-
- private void CheckDisposed()
- {
- if (_disposed)
- {
- throw new ObjectDisposedException(GetType().FullName);
- }
- }
-
- private void CheckDisposedOrStarted()
- {
- CheckDisposed();
- if (_anyOperationStarted)
- {
- throw new InvalidOperationException(SR.net_http_operation_started);
- }
- }
-
- private static void ThrowIfCURLEError(CURLcode error)
- {
- if (error != CURLcode.CURLE_OK) // success
- {
- string msg = CurlException.GetCurlErrorString((int)error, isMulti: false);
- EventSourceTrace(msg);
- switch (error)
- {
- case CURLcode.CURLE_OPERATION_TIMEDOUT:
- throw new OperationCanceledException(msg);
-
- case CURLcode.CURLE_OUT_OF_MEMORY:
- throw new OutOfMemoryException(msg);
-
- case CURLcode.CURLE_SEND_FAIL_REWIND:
- throw new InvalidOperationException(msg);
-
- default:
- throw new CurlException((int)error, msg);
- }
- }
- }
-
- private static void ThrowIfCURLMError(CURLMcode error)
- {
- if (error != CURLMcode.CURLM_OK && // success
- error != CURLMcode.CURLM_CALL_MULTI_PERFORM) // success + a hint to try curl_multi_perform again
- {
- string msg = CurlException.GetCurlErrorString((int)error, isMulti: true);
- EventSourceTrace(msg);
- switch (error)
- {
- case CURLMcode.CURLM_ADDED_ALREADY:
- case CURLMcode.CURLM_BAD_EASY_HANDLE:
- case CURLMcode.CURLM_BAD_HANDLE:
- case CURLMcode.CURLM_BAD_SOCKET:
- throw new ArgumentException(msg);
- case CURLMcode.CURLM_UNKNOWN_OPTION:
- throw new ArgumentOutOfRangeException(msg);
- case CURLMcode.CURLM_OUT_OF_MEMORY:
- throw new OutOfMemoryException(msg);
- case CURLMcode.CURLM_INTERNAL_ERROR:
- default:
- throw new CurlException((int)error, msg);
- }
- }
- }
-
- private static bool AreEqualNetworkCredentials(NetworkCredential credential1, NetworkCredential credential2)
- {
- Debug.Assert(credential1 != null && credential2 != null, "arguments are non-null in network equality check");
- return credential1.UserName == credential2.UserName &&
- credential1.Domain == credential2.Domain &&
- string.Equals(credential1.Password, credential2.Password, StringComparison.Ordinal);
- }
-
- // PERF NOTE:
- // These generic overloads of EventSourceTrace (and similar wrapper methods in some of the other CurlHandler
- // nested types) exist to allow call sites to call EventSourceTrace without boxing and without checking
- // NetEventSource.IsEnabled. Do not remove these without fixing the call sites accordingly.
-
- private static void EventSourceTrace<TArg0>(
- string formatMessage, TArg0 arg0,
- MultiAgent agent = null, EasyRequest easy = null, [CallerMemberName] string memberName = null)
- {
- if (NetEventSource.IsEnabled)
- {
- EventSourceTraceCore(string.Format(formatMessage, arg0), agent, easy, memberName);
- }
- }
-
- private static void EventSourceTrace<TArg0, TArg1, TArg2>
- (string formatMessage, TArg0 arg0, TArg1 arg1, TArg2 arg2,
- MultiAgent agent = null, EasyRequest easy = null, [CallerMemberName] string memberName = null)
- {
- if (NetEventSource.IsEnabled)
- {
- EventSourceTraceCore(string.Format(formatMessage, arg0, arg1, arg2), agent, easy, memberName);
- }
- }
-
- private static void EventSourceTrace(
- string message,
- MultiAgent agent = null, EasyRequest easy = null, [CallerMemberName] string memberName = null)
- {
- if (NetEventSource.IsEnabled)
- {
- EventSourceTraceCore(message, agent, easy, memberName);
- }
- }
-
- private static void EventSourceTraceCore(string message, MultiAgent agent, EasyRequest easy, string memberName)
- {
- // If we weren't handed a multi agent, see if we can get one from the EasyRequest
- if (agent == null && easy != null)
- {
- agent = easy._associatedMultiAgent;
- }
-
- NetEventSource.Log.HandlerMessage(
- agent?.GetHashCode() ?? 0,
- (agent?.RunningWorkerId).GetValueOrDefault(),
- easy?.Task.Id ?? 0,
- memberName,
- message);
- }
-
- private static HttpRequestException CreateHttpRequestException(Exception inner)
- {
- return new HttpRequestException(SR.net_http_client_execution_error, inner);
- }
-
- private static IOException MapToReadWriteIOException(Exception error, bool isRead)
- {
- return new IOException(
- isRead ? SR.net_http_io_read : SR.net_http_io_write,
- error is HttpRequestException && error.InnerException != null ? error.InnerException : error);
- }
-
- private static void SetChunkedModeForSend(HttpRequestMessage request)
- {
- bool chunkedMode = request.Headers.TransferEncodingChunked.GetValueOrDefault();
- HttpContent requestContent = request.Content;
- Debug.Assert(requestContent != null, "request is null");
-
- // Deal with conflict between 'Content-Length' vs. 'Transfer-Encoding: chunked' semantics.
- // libcurl adds a Transfer-Encoding header by default and the request fails if both are set.
- // ISSUE: 25163
- // Ideally we want to avoid modifying the users request message.
- if (requestContent.Headers.ContentLength.HasValue)
- {
- if (chunkedMode)
- {
- // Same behaviour as WinHttpHandler
- requestContent.Headers.ContentLength = null;
- }
- else
- {
- // Prevent libcurl from adding Transfer-Encoding header
- request.Headers.TransferEncodingChunked = false;
- }
- }
- else if (!chunkedMode)
- {
- // Make sure Transfer-Encoding: chunked header is set,
- // as we have content to send but no known length for it.
- request.Headers.TransferEncodingChunked = true;
- }
- }
-
- #endregion
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-
-namespace System.Net.Http
-{
- internal readonly struct CurlResponseHeaderReader
- {
- private const string HttpPrefix = "HTTP/";
-
- private readonly HeaderBufferSpan _span;
-
- public CurlResponseHeaderReader(IntPtr buffer, ulong size)
- {
- Debug.Assert(buffer != IntPtr.Zero);
- Debug.Assert(size <= int.MaxValue);
-
- _span = new HeaderBufferSpan(buffer, (int)size).Trim();
- }
-
- public bool ReadStatusLine(HttpResponseMessage response)
- {
- if (!_span.StartsWithHttpPrefix())
- {
- return false;
- }
-
- int index = HttpPrefix.Length;
- int majorVersion = _span.ReadInt(ref index);
- CheckResponseMsgFormat(majorVersion != 0);
- CheckResponseMsgFormat(index < _span.Length);
-
- int minorVersion;
- if (_span[index] == '.')
- {
- index++;
-
- CheckResponseMsgFormat(index < _span.Length && _span[index] >= '0' && _span[index] <= '9');
- minorVersion = _span.ReadInt(ref index);
- }
- else
- {
- minorVersion = 0;
- }
-
- CheckResponseMsgFormat(_span.SkipSpace(ref index));
-
- // Parse status code.
- int statusCode = _span.ReadInt(ref index);
- CheckResponseMsgFormat(statusCode >= 100 && statusCode < 1000);
-
- bool foundSpace = _span.SkipSpace(ref index);
- CheckResponseMsgFormat(index <= _span.Length);
- CheckResponseMsgFormat(foundSpace || index == _span.Length);
-
- // Set the response HttpVersion.
- response.Version =
- (majorVersion == 1 && minorVersion == 1) ? HttpVersion.Version11 :
- (majorVersion == 1 && minorVersion == 0) ? HttpVersion.Version10 :
- (majorVersion == 2 && minorVersion == 0) ? HttpVersion.Version20 :
- HttpVersion.Unknown;
-
- response.StatusCode = (HttpStatusCode)statusCode;
-
- // Try to use a known reason phrase instead of allocating a new string.
- HeaderBufferSpan reasonPhraseSpan = _span.Slice(index);
- string knownReasonPhrase = HttpStatusDescription.Get(response.StatusCode);
- response.ReasonPhrase = reasonPhraseSpan.EqualsOrdinal(knownReasonPhrase) ?
- knownReasonPhrase :
- reasonPhraseSpan.ToString();
-
- return true;
- }
-
- public bool ReadHeader(out string headerName, out string headerValue)
- {
- int index = 0;
- while (index < _span.Length && ValidHeaderNameChar(_span[index]))
- {
- index++;
- }
-
- if (index > 0)
- {
- // For compatability, skip past any whitespace before the colon, even though
- // the RFC suggests there shouldn't be any.
- int headerNameLength = index;
- while (index < _span.Length && IsWhiteSpaceLatin1(_span[index]))
- {
- index++;
- }
-
- CheckResponseMsgFormat(index < _span.Length);
- CheckResponseMsgFormat(_span[index] == ':');
- HeaderBufferSpan headerNameSpan = _span.Slice(0, headerNameLength);
- if (!HttpKnownHeaderNames.TryGetHeaderName(headerNameSpan.Buffer, headerNameSpan.Length, out headerName))
- {
- headerName = headerNameSpan.ToString();
- }
- CheckResponseMsgFormat(headerName.Length > 0);
-
- index++;
- headerValue = _span.Slice(index).Trim().ToString();
- return true;
- }
-
- headerName = null;
- headerValue = null;
- return false;
- }
-
- private static void CheckResponseMsgFormat(bool condition)
- {
- if (!condition)
- {
- throw new HttpRequestException(SR.net_http_invalid_response);
- }
- }
-
- private static bool ValidHeaderNameChar(byte c)
- {
- const string invalidChars = "()<>@,;:\\\"/[]?={}";
- return c > ' ' && !invalidChars.Contains((char)c);
- }
-
- internal static bool IsWhiteSpaceLatin1(byte c)
- {
- // SPACE
- // U+0009 = <control> HORIZONTAL TAB
- // U+000a = <control> LINE FEED
- // U+000b = <control> VERTICAL TAB
- // U+000c = <control> FORM FEED
- // U+000d = <control> CARRIAGE RETURN
- // U+0085 = <control> NEXT LINE
- // U+00a0 = NO-BREAK SPACE
- return c == ' ' || (c >= '\x0009' && c <= '\x000d') || c == '\x00a0' || c == '\x0085';
- }
-
- private readonly unsafe struct HeaderBufferSpan
- {
- private readonly byte* _pointer;
- public readonly int Length;
-
- public static readonly HeaderBufferSpan Empty = default(HeaderBufferSpan);
-
- public HeaderBufferSpan(IntPtr pointer, int length)
- : this((byte*)pointer, length)
- {
- }
-
- public HeaderBufferSpan(byte* pointer, int length)
- {
- Debug.Assert(pointer != null);
- Debug.Assert(length >= 0);
-
- _pointer = pointer;
- Length = length;
- }
-
- public IntPtr Buffer => new IntPtr(_pointer);
-
- public byte this[int index]
- {
- get
- {
- Debug.Assert(index >= 0 && index < Length);
- return _pointer[index];
- }
- }
-
- public HeaderBufferSpan Trim()
- {
- if (Length == 0)
- {
- return Empty;
- }
-
- int index = 0;
- while (index < Length && IsWhiteSpaceLatin1(_pointer[index]))
- {
- index++;
- }
-
- int end = Length - 1;
- while (end >= index && IsWhiteSpaceLatin1(_pointer[end]))
- {
- end--;
- }
-
- byte* pointer = _pointer + index;
- int length = end - index + 1;
-
- return new HeaderBufferSpan(pointer, length);
- }
-
- public bool StartsWithHttpPrefix()
- {
- if (Length < HttpPrefix.Length)
- {
- return false;
- }
-
- return
- (_pointer[0] == 'H' || _pointer[0] == 'h') &&
- (_pointer[1] == 'T' || _pointer[1] == 't') &&
- (_pointer[2] == 'T' || _pointer[2] == 't') &&
- (_pointer[3] == 'P' || _pointer[3] == 'p') &&
- (_pointer[4] == '/');
- }
-
- public int ReadInt(ref int index)
- {
- int value = 0;
- for (; index < Length; index++)
- {
- byte c = _pointer[index];
- if (c < '0' || c > '9')
- {
- break;
- }
-
- value = (value * 10) + (c - '0');
- }
-
- return value;
- }
-
- public bool SkipSpace(ref int index)
- {
- bool foundSpace = false;
- for (; index < Length; index++)
- {
- if (_pointer[index] == ' ' || _pointer[index] == '\t')
- {
- foundSpace = true;
- }
- else
- {
- break;
- }
- }
- return foundSpace;
- }
-
- public bool EqualsOrdinal(string value)
- {
- if (value == null)
- {
- return false;
- }
-
- if (Length != value.Length)
- {
- return false;
- }
-
- for (int i = 0; i < Length; i++)
- {
- if (_pointer[i] != value[i])
- {
- return false;
- }
- }
-
- return true;
- }
-
- public HeaderBufferSpan Slice(int startIndex)
- {
- return Slice(startIndex, Length - startIndex);
- }
-
- public HeaderBufferSpan Slice(int startIndex, int length)
- {
- Debug.Assert(startIndex >= 0);
- Debug.Assert(length >= 0);
- Debug.Assert(startIndex <= Length - length);
-
- if (length == 0)
- {
- return Empty;
- }
-
- if (startIndex == 0 && length == Length)
- {
- return this;
- }
-
- return new HeaderBufferSpan(_pointer + startIndex, length);
- }
-
- public override string ToString()
- {
- return Length == 0 ? string.Empty : HttpRuleParser.DefaultHttpEncoding.GetString(_pointer, Length);
- }
- }
- }
-}
{
public partial class HttpClientHandler : HttpMessageHandler
{
- // Only one of these two handlers will be initialized.
- private readonly CurlHandler _curlHandler;
private readonly SocketsHttpHandler _socketsHttpHandler;
private readonly DiagnosticsHandler _diagnosticsHandler;
private ClientCertificateOption _clientCertificateOptions;
- public HttpClientHandler() : this(UseSocketsHttpHandler) { }
-
- private HttpClientHandler(bool useSocketsHttpHandler) // used by parameterless ctor and as hook for testing
+ public HttpClientHandler()
{
- if (useSocketsHttpHandler)
- {
- _socketsHttpHandler = new SocketsHttpHandler();
- _diagnosticsHandler = new DiagnosticsHandler(_socketsHttpHandler);
- ClientCertificateOptions = ClientCertificateOption.Manual;
- }
- else
- {
- _curlHandler = new CurlHandler();
- _diagnosticsHandler = new DiagnosticsHandler(_curlHandler);
- }
+ _socketsHttpHandler = new SocketsHttpHandler();
+ _diagnosticsHandler = new DiagnosticsHandler(_socketsHttpHandler);
+ ClientCertificateOptions = ClientCertificateOption.Manual;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
- ((HttpMessageHandler)_curlHandler ?? _socketsHttpHandler).Dispose();
+ _socketsHttpHandler.Dispose();
}
base.Dispose(disposing);
}
- public virtual bool SupportsAutomaticDecompression => _curlHandler == null || _curlHandler.SupportsAutomaticDecompression;
+ public virtual bool SupportsAutomaticDecompression => true;
public virtual bool SupportsProxy => true;
public bool UseCookies
{
- get => _curlHandler != null ? _curlHandler.UseCookies : _socketsHttpHandler.UseCookies;
- set
- {
- if (_curlHandler != null)
- {
- _curlHandler.UseCookies = value;
- }
- else
- {
- _socketsHttpHandler.UseCookies = value;
- }
- }
+ get => _socketsHttpHandler.UseCookies;
+ set => _socketsHttpHandler.UseCookies = value;
}
public CookieContainer CookieContainer
{
- get => _curlHandler != null ? _curlHandler.CookieContainer : _socketsHttpHandler.CookieContainer;
+ get => _socketsHttpHandler.CookieContainer;
set
{
if (value == null)
throw new ArgumentNullException(nameof(value));
}
- if (_curlHandler != null)
- {
- _curlHandler.CookieContainer = value;
- }
- else
- {
- _socketsHttpHandler.CookieContainer = value;
- }
+ _socketsHttpHandler.CookieContainer = value;
}
}
public ClientCertificateOption ClientCertificateOptions
{
- get
- {
- if (_curlHandler != null)
- {
- return _curlHandler.ClientCertificateOptions;
- }
- else
- {
- return _clientCertificateOptions;
- }
- }
+ get => _clientCertificateOptions;
set
{
- if (_curlHandler != null)
+ switch (value)
{
- _curlHandler.ClientCertificateOptions = value;
- }
- else
- {
- switch (value)
- {
- case ClientCertificateOption.Manual:
- ThrowForModifiedManagedSslOptionsIfStarted();
- _clientCertificateOptions = value;
- _socketsHttpHandler.SslOptions.LocalCertificateSelectionCallback = (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => CertificateHelper.GetEligibleClientCertificate(ClientCertificates);
- break;
+ case ClientCertificateOption.Manual:
+ ThrowForModifiedManagedSslOptionsIfStarted();
+ _clientCertificateOptions = value;
+ _socketsHttpHandler.SslOptions.LocalCertificateSelectionCallback = (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => CertificateHelper.GetEligibleClientCertificate(ClientCertificates);
+ break;
- case ClientCertificateOption.Automatic:
- ThrowForModifiedManagedSslOptionsIfStarted();
- _clientCertificateOptions = value;
- _socketsHttpHandler.SslOptions.LocalCertificateSelectionCallback = (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => CertificateHelper.GetEligibleClientCertificate();
- break;
+ case ClientCertificateOption.Automatic:
+ ThrowForModifiedManagedSslOptionsIfStarted();
+ _clientCertificateOptions = value;
+ _socketsHttpHandler.SslOptions.LocalCertificateSelectionCallback = (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => CertificateHelper.GetEligibleClientCertificate();
+ break;
- default:
- throw new ArgumentOutOfRangeException(nameof(value));
- }
+ default:
+ throw new ArgumentOutOfRangeException(nameof(value));
}
}
}
{
get
{
- if (_curlHandler != null)
+ if (ClientCertificateOptions != ClientCertificateOption.Manual)
{
- return _curlHandler.ClientCertificates;
+ throw new InvalidOperationException(SR.Format(SR.net_http_invalid_enable_first, nameof(ClientCertificateOptions), nameof(ClientCertificateOption.Manual)));
}
- else
- {
- if (ClientCertificateOptions != ClientCertificateOption.Manual)
- {
- throw new InvalidOperationException(SR.Format(SR.net_http_invalid_enable_first, nameof(ClientCertificateOptions), nameof(ClientCertificateOption.Manual)));
- }
- return _socketsHttpHandler.SslOptions.ClientCertificates ??
- (_socketsHttpHandler.SslOptions.ClientCertificates = new X509CertificateCollection());
- }
+ return _socketsHttpHandler.SslOptions.ClientCertificates ??
+ (_socketsHttpHandler.SslOptions.ClientCertificates = new X509CertificateCollection());
}
}
public Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> ServerCertificateCustomValidationCallback
{
- get
- {
- return _curlHandler != null ?
- _curlHandler.ServerCertificateCustomValidationCallback :
- (_socketsHttpHandler.SslOptions.RemoteCertificateValidationCallback?.Target as ConnectHelper.CertificateCallbackMapper)?.FromHttpClientHandler;
- }
+ get => (_socketsHttpHandler.SslOptions.RemoteCertificateValidationCallback?.Target as ConnectHelper.CertificateCallbackMapper)?.FromHttpClientHandler;
set
{
- if (_curlHandler != null)
- {
- _curlHandler.ServerCertificateCustomValidationCallback = value;
- }
- else
- {
- ThrowForModifiedManagedSslOptionsIfStarted();
- _socketsHttpHandler.SslOptions.RemoteCertificateValidationCallback = value != null ?
- new ConnectHelper.CertificateCallbackMapper(value).ForSocketsHttpHandler :
- null;
- }
+ ThrowForModifiedManagedSslOptionsIfStarted();
+ _socketsHttpHandler.SslOptions.RemoteCertificateValidationCallback = value != null ?
+ new ConnectHelper.CertificateCallbackMapper(value).ForSocketsHttpHandler :
+ null;
}
}
public bool CheckCertificateRevocationList
{
- get => _curlHandler != null ? _curlHandler.CheckCertificateRevocationList : _socketsHttpHandler.SslOptions.CertificateRevocationCheckMode == X509RevocationMode.Online;
+ get => _socketsHttpHandler.SslOptions.CertificateRevocationCheckMode == X509RevocationMode.Online;
set
{
- if (_curlHandler != null)
- {
- _curlHandler.CheckCertificateRevocationList = value;
- }
- else
- {
- ThrowForModifiedManagedSslOptionsIfStarted();
- _socketsHttpHandler.SslOptions.CertificateRevocationCheckMode = value ? X509RevocationMode.Online : X509RevocationMode.NoCheck;
- }
+ ThrowForModifiedManagedSslOptionsIfStarted();
+ _socketsHttpHandler.SslOptions.CertificateRevocationCheckMode = value ? X509RevocationMode.Online : X509RevocationMode.NoCheck;
}
}
public SslProtocols SslProtocols
{
- get => _curlHandler != null ? _curlHandler.SslProtocols : _socketsHttpHandler.SslOptions.EnabledSslProtocols;
+ get => _socketsHttpHandler.SslOptions.EnabledSslProtocols;
set
{
- if (_curlHandler != null)
- {
- _curlHandler.SslProtocols = value;
- }
- else
- {
- ThrowForModifiedManagedSslOptionsIfStarted();
- _socketsHttpHandler.SslOptions.EnabledSslProtocols = value;
- }
+ ThrowForModifiedManagedSslOptionsIfStarted();
+ _socketsHttpHandler.SslOptions.EnabledSslProtocols = value;
}
}
public DecompressionMethods AutomaticDecompression
{
- get => _curlHandler != null ? _curlHandler.AutomaticDecompression : _socketsHttpHandler.AutomaticDecompression;
- set
- {
- if (_curlHandler != null)
- {
- _curlHandler.AutomaticDecompression = value;
- }
- else
- {
- _socketsHttpHandler.AutomaticDecompression = value;
- }
- }
+ get => _socketsHttpHandler.AutomaticDecompression;
+ set => _socketsHttpHandler.AutomaticDecompression = value;
}
public bool UseProxy
{
- get => _curlHandler != null ? _curlHandler.UseProxy : _socketsHttpHandler.UseProxy;
- set
- {
- if (_curlHandler != null)
- {
- _curlHandler.UseProxy = value;
- }
- else
- {
- _socketsHttpHandler.UseProxy = value;
- }
- }
+ get => _socketsHttpHandler.UseProxy;
+ set => _socketsHttpHandler.UseProxy = value;
}
public IWebProxy Proxy
{
- get => _curlHandler != null ? _curlHandler.Proxy : _socketsHttpHandler.Proxy;
- set
- {
- if (_curlHandler != null)
- {
- _curlHandler.Proxy = value;
- }
- else
- {
- _socketsHttpHandler.Proxy = value;
- }
- }
+ get => _socketsHttpHandler.Proxy;
+ set => _socketsHttpHandler.Proxy = value;
}
public ICredentials DefaultProxyCredentials
{
- get => _curlHandler != null ? _curlHandler.DefaultProxyCredentials : _socketsHttpHandler.DefaultProxyCredentials;
- set
- {
- if (_curlHandler != null)
- {
- _curlHandler.DefaultProxyCredentials = value;
- }
- else
- {
- _socketsHttpHandler.DefaultProxyCredentials = value;
- }
- }
+ get => _socketsHttpHandler.DefaultProxyCredentials;
+ set => _socketsHttpHandler.DefaultProxyCredentials = value;
}
public bool PreAuthenticate
{
- get => _curlHandler != null ? _curlHandler.PreAuthenticate : _socketsHttpHandler.PreAuthenticate;
- set
- {
- if (_curlHandler != null)
- {
- _curlHandler.PreAuthenticate = value;
- }
- else
- {
- _socketsHttpHandler.PreAuthenticate = value;
- }
- }
+ get => _socketsHttpHandler.PreAuthenticate;
+ set => _socketsHttpHandler.PreAuthenticate = value;
}
public bool UseDefaultCredentials
{
- // Either read variable from curlHandler or compare .Credentials as socketsHttpHandler does not have separate prop.
- get => _curlHandler != null ? _curlHandler.UseDefaultCredentials : _socketsHttpHandler.Credentials == CredentialCache.DefaultCredentials;
+ // Compare .Credentials as socketsHttpHandler does not have separate prop.
+ get => _socketsHttpHandler.Credentials == CredentialCache.DefaultCredentials;
set
{
- if (_curlHandler != null)
+ if (value)
{
- _curlHandler.UseDefaultCredentials = value;
+ _socketsHttpHandler.Credentials = CredentialCache.DefaultCredentials;
}
else
{
- if (value)
+ if (_socketsHttpHandler.Credentials == CredentialCache.DefaultCredentials)
{
- _socketsHttpHandler.Credentials = CredentialCache.DefaultCredentials;
- }
- else
- {
- if (_socketsHttpHandler.Credentials == CredentialCache.DefaultCredentials)
- {
- // Only clear out the Credentials property if it was a DefaultCredentials.
- _socketsHttpHandler.Credentials = null;
- }
+ // Only clear out the Credentials property if it was a DefaultCredentials.
+ _socketsHttpHandler.Credentials = null;
}
}
}
public ICredentials Credentials
{
- get => _curlHandler != null ? _curlHandler.Credentials : _socketsHttpHandler.Credentials;
- set
- {
- if (_curlHandler != null)
- {
- _curlHandler.Credentials = value;
- }
- else
- {
- _socketsHttpHandler.Credentials = value;
- }
- }
+ get => _socketsHttpHandler.Credentials;
+ set => _socketsHttpHandler.Credentials = value;
}
public bool AllowAutoRedirect
{
- get => _curlHandler != null ? _curlHandler.AllowAutoRedirect : _socketsHttpHandler.AllowAutoRedirect;
- set
- {
- if (_curlHandler != null)
- {
- _curlHandler.AllowAutoRedirect = value;
- }
- else
- {
- _socketsHttpHandler.AllowAutoRedirect = value;
- }
- }
+ get => _socketsHttpHandler.AllowAutoRedirect;
+ set => _socketsHttpHandler.AllowAutoRedirect = value;
}
public int MaxAutomaticRedirections
{
- get => _curlHandler != null ? _curlHandler.MaxAutomaticRedirections : _socketsHttpHandler.MaxAutomaticRedirections;
- set
- {
- if (_curlHandler != null)
- {
- _curlHandler.MaxAutomaticRedirections = value;
- }
- else
- {
- _socketsHttpHandler.MaxAutomaticRedirections = value;
- }
- }
+ get => _socketsHttpHandler.MaxAutomaticRedirections;
+ set => _socketsHttpHandler.MaxAutomaticRedirections = value;
}
public int MaxConnectionsPerServer
{
- get => _curlHandler != null ? _curlHandler.MaxConnectionsPerServer : _socketsHttpHandler.MaxConnectionsPerServer;
- set
- {
- if (_curlHandler != null)
- {
- _curlHandler.MaxConnectionsPerServer = value;
- }
- else
- {
- _socketsHttpHandler.MaxConnectionsPerServer = value;
- }
- }
+ get => _socketsHttpHandler.MaxConnectionsPerServer;
+ set => _socketsHttpHandler.MaxConnectionsPerServer = value;
}
public int MaxResponseHeadersLength
{
- get => _curlHandler != null ? _curlHandler.MaxResponseHeadersLength : _socketsHttpHandler.MaxResponseHeadersLength;
- set
- {
- if (_curlHandler != null)
- {
- _curlHandler.MaxResponseHeadersLength = value;
- }
- else
- {
- _socketsHttpHandler.MaxResponseHeadersLength = value;
- }
- }
+ get => _socketsHttpHandler.MaxResponseHeadersLength;
+ set => _socketsHttpHandler.MaxResponseHeadersLength = value;
}
- public IDictionary<string, object> Properties => _curlHandler != null ?
- _curlHandler.Properties :
- _socketsHttpHandler.Properties;
+ public IDictionary<string, object> Properties => _socketsHttpHandler.Properties;
protected internal override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) =>
DiagnosticsHandler.IsEnabled() ? _diagnosticsHandler.SendAsync(request, cancellationToken) :
- _curlHandler != null ? _curlHandler.SendAsync(request, cancellationToken) :
_socketsHttpHandler.SendAsync(request, cancellationToken);
}
}
string envVar = Environment.GetEnvironmentVariable(SocketsHttpHandlerEnvironmentVariableSettingName);
if (envVar != null && (envVar.Equals("false", StringComparison.OrdinalIgnoreCase) || envVar.Equals("0")))
{
- // Use WinHttpHandler on Windows and CurlHandler on Unix.
+ // Use WinHttpHandler on Windows.
return false;
}
public abstract class HttpClientEKUTest : HttpClientHandlerTestBase
{
- // Curl + OSX SecureTransport doesn't support the custom certificate callback.
- private static bool BackendSupportsCustomCertificateHandling =>
-#if TargetsWindows
- true;
-#else
- TestHelper.NativeHandlerSupportsSslConfiguration();
-#endif
-
private static bool CanTestCertificates =>
- Capability.IsTrustedRootCertificateInstalled() &&
- (BackendSupportsCustomCertificateHandling || Capability.AreHostsFileNamesInstalled());
-
- private static bool CanTestClientCertificates =>
- CanTestCertificates && BackendSupportsCustomCertificateHandling;
+ Capability.IsTrustedRootCertificateInstalled() && Capability.AreHostsFileNamesInstalled();
public const int TestTimeoutMilliseconds = 15 * 1000;
}
}
- [ConditionalFact(nameof(CanTestClientCertificates))]
+ [ConditionalFact(nameof(CanTestCertificates))]
public async Task HttpClient_NoEKUClientAuth_Ok()
{
var options = new HttpsTestServer.Options();
}
}
- [ConditionalFact(nameof(CanTestClientCertificates))]
+ [ConditionalFact(nameof(CanTestCertificates))]
public async Task HttpClient_ServerEKUClientAuth_Fails()
{
var options = new HttpsTestServer.Options();
}
}
- [OuterLoop("Uses external server")]
- [Fact]
- public async Task Automatic_SSLBackendNotSupported_ThrowsPlatformNotSupportedException()
- {
- if (BackendSupportsCustomCertificateHandling) // can't use [Conditional*] right now as it's evaluated at the wrong time for SocketsHttpHandler
- {
- return;
- }
-
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.ClientCertificateOptions = ClientCertificateOption.Automatic;
- await Assert.ThrowsAsync<PlatformNotSupportedException>(() => client.GetAsync(Configuration.Http.SecureRemoteEchoServer));
- }
- }
-
- [OuterLoop("Uses external server")]
- [Fact]
- public async Task Manual_SSLBackendNotSupported_ThrowsPlatformNotSupportedException()
- {
- if (BackendSupportsCustomCertificateHandling) // can't use [Conditional*] right now as it's evaluated at the wrong time for SocketsHttpHandler
- {
- return;
- }
-
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.ClientCertificates.Add(Configuration.Certificates.GetClientCertificate());
- using (HttpClient client = CreateHttpClient(handler))
- {
- await Assert.ThrowsAsync<PlatformNotSupportedException>(() => client.GetAsync(Configuration.Http.SecureRemoteEchoServer));
- }
- }
-
private HttpClient CreateHttpClientWithCert(X509Certificate2 cert)
{
HttpClientHandler handler = CreateHttpClientHandler();
[InlineData(3, false)]
public async Task Manual_CertificateOnlySentWhenValid_Success(int certIndex, bool serverExpectsClientCertificate)
{
- if (!BackendSupportsCustomCertificateHandling || IsCurlHandler) // can't use [Conditional*] right now as it's evaluated at the wrong time for SocketsHttpHandler
- {
- _output.WriteLine($"Skipping {nameof(Manual_CertificateOnlySentWhenValid_Success)}()");
- return;
- }
-
var options = new LoopbackServer.Options { UseSsl = true };
X509Certificate2 GetClientCertificate(int certIndex) => certIndex switch
int numberOfRequests,
bool reuseClient) // validate behavior with and without connection pooling, which impacts client cert usage
{
- if (!BackendSupportsCustomCertificateHandling) // can't use [Conditional*] right now as it's evaluated at the wrong time for SocketsHttpHandler
- {
- _output.WriteLine($"Skipping {nameof(Manual_CertificateSentMatchesCertificateReceived_Success)}()");
- return;
- }
-
var options = new LoopbackServer.Options { UseSsl = true };
async Task MakeAndValidateRequest(HttpClient client, LoopbackServer server, Uri url, X509Certificate2 cert)
[InlineData(ClientCertificateOption.Automatic)]
public async Task AutomaticOrManual_DoesntFailRegardlessOfWhetherClientCertsAreAvailable(ClientCertificateOption mode)
{
- if (!BackendSupportsCustomCertificateHandling) // can't use [Conditional*] right now as it's evaluated at the wrong time for SocketsHttpHandler
- {
- _output.WriteLine($"Skipping {nameof(AutomaticOrManual_DoesntFailRegardlessOfWhetherClientCertsAreAvailable)}()");
- return;
- }
-
using (HttpClientHandler handler = CreateHttpClientHandler())
using (HttpClient client = CreateHttpClient(handler))
{
}, new LoopbackServer.Options { UseSsl = true });
}
}
-
- private bool BackendSupportsCustomCertificateHandling
- {
- get
- {
-#if TargetsWindows
- return true;
-#else
- if (UseSocketsHttpHandler)
- {
- // Socket Handler is independent of platform curl.
- return true;
- }
-
- return TestHelper.NativeHandlerSupportsSslConfiguration();
-#endif
- }
- }
}
}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Net.Security;
-using System.Net.Test.Common;
-using System.Runtime.InteropServices;
-using System.Security.Authentication.ExtendedProtection;
-using System.Security.Cryptography.X509Certificates;
-using System.Threading.Tasks;
-using Microsoft.DotNet.RemoteExecutor;
-using Xunit;
-
-namespace System.Net.Http.Functional.Tests
-{
- public abstract partial class HttpClientHandler_ServerCertificates_Test
- {
- private static bool ShouldSuppressRevocationException
- {
- get
- {
- if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- {
- return false;
- }
-
- // If a run on a clean macOS ever fails we need to consider that "false"
- // for CheckCertificateRevocationList is actually "use a system default" now,
- // and may require changing how this option is exposed. Considering the variety of
- // systems this should probably be complex like
- // enum RevocationCheckingOption {
- // // Use it if able
- // BestPlatformSecurity = 0,
- // // Don't use it, if that's an option.
- // BestPlatformPerformance,
- // // Required
- // MustCheck,
- // // Prohibited
- // MustNotCheck,
- // }
-
- if (Interop.Http.GetSslVersionDescription() == "SecureTransport")
- {
- return true;
- }
- return false;
- }
- }
-
- internal bool BackendSupportsCustomCertificateHandling
- {
- get
- {
- if (UseSocketsHttpHandler)
- {
- return true;
- }
-
- return TestHelper.NativeHandlerSupportsSslConfiguration();
- }
- }
-
- [Fact]
- [PlatformSpecific(~TestPlatforms.OSX)] // Not implemented
- public void HttpClientUsesSslCertEnvironmentVariables()
- {
- // We set SSL_CERT_DIR and SSL_CERT_FILE to empty locations.
- // The HttpClient should fail to validate the server certificate.
-
- var psi = new ProcessStartInfo();
- string sslCertDir = GetTestFilePath();
- Directory.CreateDirectory(sslCertDir);
- psi.Environment.Add("SSL_CERT_DIR", sslCertDir);
-
- string sslCertFile = GetTestFilePath();
- File.WriteAllText(sslCertFile, "");
- psi.Environment.Add("SSL_CERT_FILE", sslCertFile);
-
- RemoteExecutor.Invoke(async (useSocketsHttpHandlerString, useHttp2String) =>
- {
- const string Url = "https://www.microsoft.com";
-
- using (HttpClient client = CreateHttpClient(useSocketsHttpHandlerString, useHttp2String))
- {
- await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(Url));
- }
- }, UseSocketsHttpHandler.ToString(), UseHttp2.ToString(), new RemoteInvokeOptions { StartInfo = psi }).Dispose();
- }
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-namespace System.Net.Http.Functional.Tests
-{
- public abstract partial class HttpClientHandler_ServerCertificates_Test
- {
- private static bool ShouldSuppressRevocationException => false;
-
- internal bool BackendSupportsCustomCertificateHandling => true;
- }
-}
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
using System.Linq;
using System.Net.Security;
using System.Net.Test.Common;
public abstract partial class HttpClientHandler_ServerCertificates_Test : HttpClientHandlerTestBase
{
private static bool ClientSupportsDHECipherSuites => (!PlatformDetection.IsWindows || PlatformDetection.IsWindows10Version1607OrGreater);
- private bool BackendSupportsCustomCertificateHandlingAndClientSupportsDHECipherSuites =>
- (BackendSupportsCustomCertificateHandling && ClientSupportsDHECipherSuites);
public HttpClientHandler_ServerCertificates_Test(ITestOutputHelper output) : base(output) { }
[Fact]
public async Task UseCallback_HaveCredsAndUseAuthenticatedCustomProxyAndPostToSecureServer_Success()
{
- if (!BackendSupportsCustomCertificateHandling)
- {
- return;
- }
-
if (IsWinHttpHandler && PlatformDetection.IsWindows7)
{
// Issue #27612
[Fact]
public async Task UseCallback_HaveNoCredsAndUseAuthenticatedCustomProxyAndPostToSecureServer_ProxyAuthenticationRequiredStatusCode()
{
- if (!BackendSupportsCustomCertificateHandling)
- {
- return;
- }
-
var options = new LoopbackProxyServer.Options
{ AuthenticationSchemes = AuthenticationSchemes.Basic,
ConnectionCloseAfter407 = true
[Fact]
public async Task UseCallback_NotSecureConnection_CallbackNotCalled()
{
- if (!BackendSupportsCustomCertificateHandling)
- {
- Console.WriteLine($"Skipping {nameof(UseCallback_NotSecureConnection_CallbackNotCalled)}()");
- return;
- }
-
HttpClientHandler handler = CreateHttpClientHandler();
using (HttpClient client = CreateHttpClient(handler))
{
[MemberData(nameof(UseCallback_ValidCertificate_ExpectedValuesDuringCallback_Urls))]
public async Task UseCallback_ValidCertificate_ExpectedValuesDuringCallback(Configuration.Http.RemoteServer remoteServer, Uri url, bool checkRevocation)
{
- if (!BackendSupportsCustomCertificateHandling)
- {
- Console.WriteLine($"Skipping {nameof(UseCallback_ValidCertificate_ExpectedValuesDuringCallback)}({url}, {checkRevocation})");
- return;
- }
-
HttpClientHandler handler = CreateHttpClientHandler();
using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
{
[Fact]
public async Task UseCallback_CallbackReturnsFailure_ThrowsException()
{
- if (!BackendSupportsCustomCertificateHandling)
- {
- Console.WriteLine($"Skipping {nameof(UseCallback_CallbackReturnsFailure_ThrowsException)}()");
- return;
- }
-
HttpClientHandler handler = CreateHttpClientHandler();
using (HttpClient client = CreateHttpClient(handler))
{
[Fact]
public async Task UseCallback_CallbackThrowsException_ExceptionPropagatesAsBaseException()
{
- if (!BackendSupportsCustomCertificateHandling)
- {
- Console.WriteLine($"Skipping {nameof(UseCallback_CallbackThrowsException_ExceptionPropagatesAsBaseException)}()");
- return;
- }
-
HttpClientHandler handler = CreateHttpClientHandler();
using (HttpClient client = CreateHttpClient(handler))
{
}
catch (HttpRequestException)
{
- if (UseSocketsHttpHandler || !ShouldSuppressRevocationException)
+ if (UseSocketsHttpHandler)
throw;
}
}
[Fact]
public async Task NoCallback_RevokedCertificate_RevocationChecking_Fails()
{
- if (!BackendSupportsCustomCertificateHandling)
- {
- Console.WriteLine($"Skipping {nameof(NoCallback_RevokedCertificate_RevocationChecking_Fails)}()");
- return;
- }
-
HttpClientHandler handler = CreateHttpClientHandler();
handler.CheckCertificateRevocationList = true;
using (HttpClient client = CreateHttpClient(handler))
private async Task UseCallback_BadCertificate_ExpectedPolicyErrors_Helper(string url, string useSocketsHttpHandlerString, string useHttp2String, SslPolicyErrors expectedErrors)
{
- if (!BackendSupportsCustomCertificateHandling)
- {
- Console.WriteLine($"Skipping {nameof(UseCallback_BadCertificate_ExpectedPolicyErrors)}({url}, {expectedErrors})");
- return;
- }
-
HttpClientHandler handler = CreateHttpClientHandler(useSocketsHttpHandlerString, useHttp2String);
using (HttpClient client = CreateHttpClient(handler, useHttp2String))
{
{
const int SEC_E_BUFFER_TOO_SMALL = unchecked((int)0x80090321);
- if (!BackendSupportsCustomCertificateHandlingAndClientSupportsDHECipherSuites)
+ if (!ClientSupportsDHECipherSuites)
{
return;
}
}
[OuterLoop("Uses external server")]
- [Fact]
- public async Task SSLBackendNotSupported_Callback_ThrowsPlatformNotSupportedException()
- {
- if (BackendSupportsCustomCertificateHandling)
- {
- return;
- }
-
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.ServerCertificateCustomValidationCallback = delegate { return true; }; // Do not use TestHelper.AllowAllCertificates / HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
- using (HttpClient client = CreateHttpClient(handler))
- {
- await Assert.ThrowsAsync<PlatformNotSupportedException>(() => client.GetAsync(Configuration.Http.SecureRemoteEchoServer));
- }
- }
-
- [OuterLoop("Uses external server")]
- [Fact]
- // For macOS the "custom handling" means that revocation can't be *disabled*. So this test does not apply.
- [PlatformSpecific(~TestPlatforms.OSX)]
- public async Task SSLBackendNotSupported_Revocation_ThrowsPlatformNotSupportedException()
- {
- if (BackendSupportsCustomCertificateHandling)
- {
- return;
- }
-
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.CheckCertificateRevocationList = true;
- using (HttpClient client = CreateHttpClient(handler))
- {
- await Assert.ThrowsAsync<PlatformNotSupportedException>(() => client.GetAsync(Configuration.Http.SecureRemoteEchoServer));
- }
- }
-
- [OuterLoop("Uses external server")]
[PlatformSpecific(TestPlatforms.Windows)] // CopyToAsync(Stream, TransportContext) isn't used on unix
[Fact]
public async Task PostAsync_Post_ChannelBinding_ConfiguredCorrectly()
Assert.NotNull(channelBinding);
// Validate the ChannelBinding's validity.
- if (BackendSupportsCustomCertificateHandling)
+ Assert.False(channelBinding.IsInvalid, "Expected valid binding");
+ Assert.NotEqual(IntPtr.Zero, channelBinding.DangerousGetHandle());
+
+ // Validate the ChannelBinding's description.
+ string channelBindingDescription = channelBinding.ToString();
+ Assert.NotNull(channelBindingDescription);
+ Assert.NotEmpty(channelBindingDescription);
+ Assert.True((channelBindingDescription.Length + 1) % 3 == 0, $"Unexpected length {channelBindingDescription.Length}");
+ for (int i = 0; i < channelBindingDescription.Length; i++)
{
- Assert.False(channelBinding.IsInvalid, "Expected valid binding");
- Assert.NotEqual(IntPtr.Zero, channelBinding.DangerousGetHandle());
-
- // Validate the ChannelBinding's description.
- string channelBindingDescription = channelBinding.ToString();
- Assert.NotNull(channelBindingDescription);
- Assert.NotEmpty(channelBindingDescription);
- Assert.True((channelBindingDescription.Length + 1) % 3 == 0, $"Unexpected length {channelBindingDescription.Length}");
- for (int i = 0; i < channelBindingDescription.Length; i++)
+ char c = channelBindingDescription[i];
+ if (i % 3 == 2)
{
- char c = channelBindingDescription[i];
- if (i % 3 == 2)
- {
- Assert.Equal(' ', c);
- }
- else
- {
- Assert.True((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'), $"Expected hex, got {c}");
- }
+ Assert.Equal(' ', c);
+ }
+ else
+ {
+ Assert.True((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'), $"Expected hex, got {c}");
}
}
- else
+ }
+ }
+
+ [Fact]
+ [PlatformSpecific(~TestPlatforms.Linux)]
+ public void HttpClientUsesSslCertEnvironmentVariables()
+ {
+ // We set SSL_CERT_DIR and SSL_CERT_FILE to empty locations.
+ // The HttpClient should fail to validate the server certificate.
+
+ var psi = new ProcessStartInfo();
+ string sslCertDir = GetTestFilePath();
+ Directory.CreateDirectory(sslCertDir);
+ psi.Environment.Add("SSL_CERT_DIR", sslCertDir);
+
+ string sslCertFile = GetTestFilePath();
+ File.WriteAllText(sslCertFile, "");
+ psi.Environment.Add("SSL_CERT_FILE", sslCertFile);
+
+ RemoteExecutor.Invoke(async (useSocketsHttpHandlerString, useHttp2String) =>
+ {
+ const string Url = "https://www.microsoft.com";
+
+ using (HttpClient client = CreateHttpClient(useSocketsHttpHandlerString, useHttp2String))
{
- // Backend doesn't support getting the details to create the CBT.
- Assert.True(channelBinding.IsInvalid, "Expected invalid binding");
- Assert.Equal(IntPtr.Zero, channelBinding.DangerousGetHandle());
- Assert.Null(channelBinding.ToString());
+ await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(Url));
}
- }
+ }, UseSocketsHttpHandler.ToString(), UseHttp2.ToString(), new RemoteInvokeOptions { StartInfo = psi }).Dispose();
}
}
}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.IO;
-using System.Net.Security;
-using System.Net.Sockets;
-using System.Net.Test.Common;
-using System.Runtime.InteropServices;
-using System.Security.Authentication;
-using System.Threading;
-using System.Threading.Tasks;
-using Xunit;
-
-namespace System.Net.Http.Functional.Tests
-{
- public abstract partial class HttpClientHandler_SslProtocols_Test
- {
- private bool BackendSupportsSslConfiguration =>
- UseSocketsHttpHandler || TestHelper.NativeHandlerSupportsSslConfiguration();
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-
-namespace System.Net.Http.Functional.Tests
-{
- public abstract partial class HttpClientHandler_SslProtocols_Test
- {
- private static bool BackendSupportsSslConfiguration => true;
- }
-}
[Fact]
public async Task SetProtocols_AfterRequest_ThrowsException()
{
- if (!BackendSupportsSslConfiguration)
- {
- return;
- }
-
using (HttpClientHandler handler = CreateHttpClientHandler())
using (HttpClient client = CreateHttpClient(handler))
{
[MemberData(nameof(GetAsync_AllowedSSLVersion_Succeeds_MemberData))]
public async Task GetAsync_AllowedSSLVersion_Succeeds(SslProtocols acceptedProtocol, bool requestOnlyThisProtocol)
{
- if (!BackendSupportsSslConfiguration)
- {
- return;
- }
-
#pragma warning disable 0618
if (IsCurlHandler && PlatformDetection.IsRedHatFamily6 && acceptedProtocol == SslProtocols.Ssl3)
{
[Fact]
public async Task GetAsync_NoSpecifiedProtocol_DefaultsToTls12()
{
- if (!BackendSupportsSslConfiguration)
- {
- return;
- }
-
using (HttpClientHandler handler = CreateHttpClientHandler())
using (HttpClient client = CreateHttpClient(handler))
{
public async Task GetAsync_AllowedClientSslVersionDiffersFromServer_ThrowsException(
SslProtocols allowedClientProtocols, SslProtocols acceptedServerProtocols)
{
- if (!BackendSupportsSslConfiguration)
- {
- return;
- }
-
if (IsWinHttpHandler &&
allowedClientProtocols == (SslProtocols.Tls11 | SslProtocols.Tls12) &&
acceptedServerProtocols == SslProtocols.Tls)
namespace System.Net.Http.Functional.Tests
{
-
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public class PlatformHandler_HttpClientHandler : HttpClientHandlerTestBase
{
protected override bool UseSocketsHttpHandler => false;
}
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_HttpClientHandler_Asynchrony_Test : HttpClientHandler_Asynchrony_Test
{
public PlatformHandler_HttpClientHandler_Asynchrony_Test(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_HttpProtocolTests : HttpProtocolTests
{
public PlatformHandler_HttpProtocolTests(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_HttpProtocolTests_Dribble : HttpProtocolTests_Dribble
{
public PlatformHandler_HttpProtocolTests_Dribble(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_DiagnosticsTest : DiagnosticsTest
{
public PlatformHandler_DiagnosticsTest(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_HttpClient_SelectedSites_Test : HttpClient_SelectedSites_Test
{
public PlatformHandler_HttpClient_SelectedSites_Test(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_HttpClientEKUTest : HttpClientEKUTest
{
public PlatformHandler_HttpClientEKUTest(ITestOutputHelper output) : base(output) { }
}
#if NETCOREAPP
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_HttpClientHandler_Decompression_Tests : HttpClientHandler_Decompression_Test
{
public PlatformHandler_HttpClientHandler_Decompression_Tests(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test : HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test
{
public PlatformHandler_HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test(ITestOutputHelper output) : base(output) { }
}
#endif
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_HttpClientHandler_ClientCertificates_Test : HttpClientHandler_ClientCertificates_Test
{
public PlatformHandler_HttpClientHandler_ClientCertificates_Test(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_HttpClientHandler_DefaultProxyCredentials_Test : HttpClientHandler_DefaultProxyCredentials_Test
{
public PlatformHandler_HttpClientHandler_DefaultProxyCredentials_Test(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_HttpClientHandler_MaxConnectionsPerServer_Test : HttpClientHandler_MaxConnectionsPerServer_Test
{
public PlatformHandler_HttpClientHandler_MaxConnectionsPerServer_Test(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_HttpClientHandler_ServerCertificates_Test : HttpClientHandler_ServerCertificates_Test
{
public PlatformHandler_HttpClientHandler_ServerCertificates_Test(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_PostScenarioTest : PostScenarioTest
{
public PlatformHandler_PostScenarioTest(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_ResponseStreamTest : ResponseStreamTest
{
public PlatformHandler_ResponseStreamTest(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_HttpClientHandler_SslProtocols_Test : HttpClientHandler_SslProtocols_Test
{
public PlatformHandler_HttpClientHandler_SslProtocols_Test(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_HttpClientHandler_Proxy_Test : HttpClientHandler_Proxy_Test
{
public PlatformHandler_HttpClientHandler_Proxy_Test(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_SchSendAuxRecordHttpTest : SchSendAuxRecordHttpTest
{
public PlatformHandler_SchSendAuxRecordHttpTest(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_HttpClientHandlerTest : HttpClientHandlerTest
{
public PlatformHandler_HttpClientHandlerTest(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandlerTest_AutoRedirect : HttpClientHandlerTest_AutoRedirect
{
public PlatformHandlerTest_AutoRedirect(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_DefaultCredentialsTest : DefaultCredentialsTest
{
public PlatformHandler_DefaultCredentialsTest(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_IdnaProtocolTests : IdnaProtocolTests
{
public PlatformHandler_IdnaProtocolTests(ITestOutputHelper output) : base(output) { }
protected override bool SupportsIdna => !PlatformDetection.IsWindows7;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_HttpRetryProtocolTests : HttpRetryProtocolTests
{
public PlatformHandler_HttpRetryProtocolTests(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandlerTest_Cookies : HttpClientHandlerTest_Cookies
{
public PlatformHandlerTest_Cookies(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandlerTest_Cookies_Http11 : HttpClientHandlerTest_Cookies_Http11
{
public PlatformHandlerTest_Cookies_Http11(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_HttpClientHandler_MaxResponseHeadersLength_Test : HttpClientHandler_MaxResponseHeadersLength_Test
{
public PlatformHandler_HttpClientHandler_MaxResponseHeadersLength_Test(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_HttpClientHandler_Cancellation_Test : HttpClientHandler_Cancellation_Test
{
public PlatformHandler_HttpClientHandler_Cancellation_Test(ITestOutputHelper output) : base(output) { }
protected override bool UseSocketsHttpHandler => false;
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandler_HttpClientHandler_Authentication_Test : HttpClientHandler_Authentication_Test
{
public PlatformHandler_HttpClientHandler_Authentication_Test(ITestOutputHelper output) : base(output) { }
// Enable this to run HTTP2 tests on platform handler
#if PLATFORM_HANDLER_HTTP2_TESTS
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class PlatformHandlerTest_Http2 : HttpClientHandlerTest_Http2
{
protected override bool UseSocketsHttpHandler => false;
}
-
+
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
[ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.SupportsAlpn))]
public sealed class PlatformHandlerTest_Cookies_Http2 : HttpClientHandlerTest_Cookies
{
}
}
+ // Test only WinHttpHandler since the CurlHandler was removed
+ [PlatformSpecific(TestPlatforms.Windows)]
public sealed class SocketsHttpHandler_ExternalConfiguration_Test : HttpClientHandlerTestBase
{
public SocketsHttpHandler_ExternalConfiguration_Test(ITestOutputHelper output) : base(output) { }
</PropertyGroup>
<ItemGroup>
<Compile Include="$(CommonPath)\Interop\Unix\Interop.Libraries.cs" Condition="'$(TargetsUnix)' == 'true'">
- <Link>Common\Interop\Unix\System.Net.Http.Native\Interop.VersionInfo.cs</Link>
- </Compile>
- <Compile Include="$(CommonPath)\Interop\Unix\System.Net.Http.Native\Interop.VersionInfo.cs" Condition="'$(TargetsUnix)' == 'true'">
- <Link>Common\Interop\Unix\System.Net.Http.Native\Interop.VersionInfo.cs</Link>
+ <Link>Common\Interop\Unix\Interop.Libraries.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.IsNtlmInstalled.cs" Condition="'$(TargetsUnix)' == 'true'">
<Link>Common\Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.IsNtlmInstalled.cs</Link>
<Compile Include="HttpClientHandlerTest.Proxy.cs" />
<Compile Include="HttpClientHandlerTest.ResponseDrain.cs" />
<Compile Include="HttpClientHandlerTest.ServerCertificates.cs" />
- <Compile Include="HttpClientHandlerTest.ServerCertificates.Unix.cs" Condition="'$(TargetsUnix)' == 'true'" />
- <Compile Include="HttpClientHandlerTest.ServerCertificates.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
<Compile Include="HttpClientHandlerTest.SslProtocols.cs" />
- <Compile Include="HttpClientHandlerTest.SslProtocols.Unix.cs" Condition="'$(TargetsUnix)' == 'true'" />
- <Compile Include="HttpClientHandlerTest.SslProtocols.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
<Compile Include="DiagnosticsTests.cs" />
<Compile Include="HttpClientHandlerTestBase.cs" />
<Compile Include="HttpClientTest.cs" />
allowUnencryptedHttp2Field.SetValue(settings, true);
}
- public static bool NativeHandlerSupportsSslConfiguration()
- {
-#if TargetsWindows
- return true;
-#else
- if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- {
- return false;
- }
-
- // For other Unix-based systems it's true if (and only if) the currect openssl backend
- // is used with libcurl.
- bool hasAnyOpenSsl =
- Interop.Http.GetSslVersionDescription()?.StartsWith(Interop.Http.OpenSslDescriptionPrefix, StringComparison.OrdinalIgnoreCase) ?? false;
-
- if (!hasAnyOpenSsl)
- {
- return false;
- }
-
- // We're on an OpenSSL-based system, with an OpenSSL backend.
- // Ask the product how it feels about this.
- Type interopHttp = typeof(HttpClient).Assembly.GetType("Interop+Http");
- PropertyInfo hasMatchingOpenSslVersion = interopHttp.GetProperty("HasMatchingOpenSslVersion", BindingFlags.Static | BindingFlags.NonPublic);
- return (bool)hasMatchingOpenSslVersion.GetValue(null);
-#endif
- }
-
public static byte[] GenerateRandomContent(int size)
{
byte[] data = new byte[size];
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Collections.Generic;
-using System.Linq;
-
-using Xunit;
-
-namespace System.Net.Http.Tests
-{
- public class CurlResponseParseUtilsTest
- {
- private const string StatusCodeTemplate = "HTTP/1.1 {0} {1}";
- private const string MissingSpaceFormat = "HTTP/1.1 {0}InvalidPhrase";
-
- private const string StatusCodeVersionFormat = "HTTP/{0}.{1} 200 OK";
- private const string StatusCodeMajorVersionOnlyFormat = "HTTP/{0} 200 OK";
-
- private const string ValidHeader = "Content-Type: text/xml; charset=utf-8";
- private const string HeaderNameWithInvalidChar = "Content{0}Type: text/xml; charset=utf-8";
-
- private const string invalidChars = "()<>@,;\\\"/[]?={} \t";
-
- public static readonly IEnumerable<object[]> ValidStatusCodeLines = GetStatusCodeLines(StatusCodeTemplate);
- public static IEnumerable<object[]> InvalidStatusCodeLines => GetStatusCodeLines(MissingSpaceFormat).Select(o => new object[] { o[0] });
- public static readonly IEnumerable<object[]> StatusCodeVersionLines = GetStatusCodeLinesForMajorVersions(1, 10).Concat(GetStatusCodeLinesForMajorMinorVersions(1, 10));
- public static readonly IEnumerable<object[]> InvalidHeaderLines = GetInvalidHeaderLines();
-
- private static IEnumerable<object[]> GetStatusCodeLines(string template)
- {
- const string reasonPhrase = "Test Phrase";
- foreach (int code in Enum.GetValues(typeof(HttpStatusCode)))
- {
- yield return new object[] { string.Format(template, code, reasonPhrase), code, reasonPhrase};
- }
- }
-
- private static IEnumerable<object[]> GetStatusCodeLinesForMajorVersions(int min, int max)
- {
- for (int major = min; major < max; major++)
- {
- yield return new object[] { string.Format(StatusCodeMajorVersionOnlyFormat, major), major, 0 };
- }
- }
-
- private static IEnumerable<object[]> GetStatusCodeLinesForMajorMinorVersions(int min, int max)
- {
- for (int major = min; major < max; major++)
- {
- for (int minor = min; minor < max; minor++)
- {
- yield return new object[] {string.Format(StatusCodeVersionFormat, major, minor), major, minor};
- }
- }
- }
-
- private static IEnumerable<object[]> GetInvalidHeaderLines()
- {
- foreach (char c in invalidChars)
- {
- yield return new object[] { string.Format(HeaderNameWithInvalidChar, c) };
- }
- }
-
- #region StatusCode
- [Theory, MemberData(nameof(ValidStatusCodeLines))]
- public unsafe void ReadStatusLine_ValidStatusCode_ResponseMessageValueSet(string statusLine, HttpStatusCode expectedCode, string expectedPhrase)
- {
- byte[] buffer = statusLine.Select(c => checked((byte)c)).ToArray();
-
- fixed (byte* pBuffer = buffer)
- {
- var reader = new CurlResponseHeaderReader(new IntPtr(pBuffer), checked((ulong)buffer.Length));
- using (var response = new HttpResponseMessage())
- {
- Assert.True(reader.ReadStatusLine(response));
- Assert.Equal(expectedCode, response.StatusCode);
- Assert.Equal(expectedPhrase, response.ReasonPhrase);
- }
- }
- }
-
- [Theory, MemberData(nameof(InvalidStatusCodeLines))]
- public unsafe void ReadStatusLine_InvalidStatusCode_ThrowsHttpRequestException(string statusLine)
- {
- byte[] buffer = statusLine.Select(c => checked((byte)c)).ToArray();
-
- fixed (byte* pBuffer = buffer)
- {
- var reader = new CurlResponseHeaderReader(new IntPtr(pBuffer), checked((ulong)buffer.Length));
- using (var response = new HttpResponseMessage())
- {
- Assert.Throws<HttpRequestException>(() => reader.ReadStatusLine(response));
- }
- }
- }
-
- [Theory, MemberData(nameof(StatusCodeVersionLines))]
- public unsafe void ReadStatusLine_ValidStatusCodeLine_ResponseMessageVersionSet(string statusLine, int major, int minor)
- {
- byte[] buffer = statusLine.Select(c => checked((byte)c)).ToArray();
-
- fixed (byte* pBuffer = buffer)
- {
- var reader = new CurlResponseHeaderReader(new IntPtr(pBuffer), checked((ulong)buffer.Length));
- using (var response = new HttpResponseMessage())
- {
- Assert.True(reader.ReadStatusLine(response));
- int expectedMajor = 0;
- int expectedMinor = 0;
- if (major == 1 && (minor == 0 || minor == 1))
- {
- expectedMajor = 1;
- expectedMinor = minor;
- }
- else if (major == 2 && minor == 0)
- {
- expectedMajor = 2;
- expectedMinor = 0;
- }
-
- Assert.Equal(expectedMajor, response.Version.Major);
- Assert.Equal(expectedMinor, response.Version.Minor);
- }
- }
- }
-
- #endregion
-
- #region Headers
- public static IEnumerable<object[]> ReadHeader_ValidHeaderLine_HeaderReturned_MemberData()
- {
- var namesAndValues = new KeyValuePair<string, string>[]
- {
- new KeyValuePair<string, string>("TestHeader", "Test header value"),
- new KeyValuePair<string, string>("TestHeader", ""),
- new KeyValuePair<string, string>("Server", "IIS"),
- new KeyValuePair<string, string>("Server", "I:I:S"),
- };
- var whitespaces = new string[] { "", " ", " ", " \t" };
-
- foreach (KeyValuePair<string, string> nameAndValue in namesAndValues)
- {
- foreach (string beforeColon in whitespaces) // only "" is valid according to the RFC, but we parse more leniently
- {
- foreach (string afterColon in whitespaces)
- {
- yield return new object[] { $"{nameAndValue.Key}{beforeColon}:{afterColon}{nameAndValue.Value}", nameAndValue.Key, nameAndValue.Value };
- }
- }
- }
- }
-
- [Theory]
- [MemberData(nameof(ReadHeader_ValidHeaderLine_HeaderReturned_MemberData))]
- public unsafe void ReadHeader_ValidHeaderLine_HeaderReturned(string headerLine, string expectedHeaderName, string expectedHeaderValue)
- {
- byte[] buffer = headerLine.Select(c => checked((byte)c)).ToArray();
- fixed (byte* pBuffer = buffer)
- {
- var reader = new CurlResponseHeaderReader(new IntPtr(pBuffer), checked((ulong)buffer.Length));
-
- string headerName;
- string headerValue;
- Assert.True(reader.ReadHeader(out headerName, out headerValue));
- Assert.Equal(expectedHeaderName, headerName);
- Assert.Equal(expectedHeaderValue, headerValue);
- }
- }
-
- [Fact]
- public unsafe void ReadHeader_EmptyBuffer_ReturnsFalse()
- {
- byte[] buffer = new byte[2]; // Non-empty array so we can get a valid pointer using fixed.
- ulong length = 0; // But a length of 0 for empty.
-
- fixed (byte* pBuffer = buffer)
- {
- var reader = new CurlResponseHeaderReader(new IntPtr(pBuffer), length);
-
- string headerName;
- string headerValue;
- Assert.False(reader.ReadHeader(out headerName, out headerValue));
- Assert.Null(headerName);
- Assert.Null(headerValue);
- }
- }
-
- [Theory, MemberData(nameof(InvalidHeaderLines))]
- public unsafe void ReadHeaderName_InvalidHeaderLine_ThrowsHttpRequestException(string headerLine)
- {
- byte[] buffer = headerLine.Select(c => checked((byte)c)).ToArray();
-
- fixed (byte* pBuffer = buffer)
- {
- var reader = new CurlResponseHeaderReader(new IntPtr(pBuffer), checked((ulong)buffer.Length));
-
- string headerName;
- string headerValue;
- Assert.Throws<HttpRequestException>(() => reader.ReadHeader(out headerName, out headerValue));
- }
- }
- #endregion
- }
-}
<Compile Include="..\..\src\System\Net\Http\StringContent.cs">
<Link>ProductionCode\System\Net\Http\StringContent.cs</Link>
</Compile>
- <Compile Include="..\..\src\System\Net\Http\CurlHandler\CurlResponseHeaderReader.cs">
- <Link>ProductionCode\System\Net\Http\CurlResponseHeaderReader.cs</Link>
- </Compile>
<Compile Include="..\..\src\System\Net\Http\SocketsHttpHandler\ArrayBuffer.cs">
<Link>ProductionCode\System\Net\Http\SocketsHttpHandler\ArrayBuffer.cs</Link>
</Compile>
<Compile Include="Headers\CacheControlHeaderValueTest.cs" />
<Compile Include="Headers\ContentDispositionHeaderValueTest.cs" />
<Compile Include="Headers\ContentRangeHeaderValueTest.cs" />
- <Compile Include="Headers\CurlResponseHeaderReaderTest.cs" />
<Compile Include="Headers\DateHeaderParserTest.cs" />
<Compile Include="Headers\EntityTagHeaderValueTest.cs" />
<Compile Include="Headers\GenericHeaderParserTest\AuthenticationParserTest.cs" />
<Compile Include="System\Net\WebExceptionPal.Windows.cs" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetsUnix)' == 'true' ">
- <Compile Include="$(CommonPath)\Interop\Unix\System.Net.Http.Native\Interop.CURLcode.cs">
- <Link>Common\Interop\Unix\System.Net.Http.Native\Interop.CURLcode.cs</Link>
- </Compile>
<Compile Include="$(CommonPath)\System\Net\ContextAwareResult.Unix.cs">
<Link>Common\System\Net\ContextAwareResult.Unix.cs</Link>
</Compile>
{
internal static WebExceptionStatus GetStatusFromException(HttpRequestException ex)
{
- WebExceptionStatus status;
-
// Issue 2384: update WebException.GetStatusFromException after System.Net.Http API changes
//
// For now, we use the .HResult of the exception to help us map to a suitable
// the underlying .NET Core and .NET Native versions of the System.Net.Http stack.
// In the future, the HttpRequestException will have its own .Status property that is
// an enum type that is more compatible directly with the WebExceptionStatus enum.
- switch (ex.HResult)
- {
- case (int)Interop.Http.CURLcode.CURLE_COULDNT_RESOLVE_HOST:
- status = WebExceptionStatus.NameResolutionFailure;
- break;
- default:
- status = GetStatusFromExceptionHelper(ex);
- break;
- }
-
- return status;
+ return GetStatusFromExceptionHelper(ex);
}
}
}