mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
use reflect to reduce code size
This commit is contained in:
@@ -1,54 +0,0 @@
|
||||
namespace Snap.Hutao.Win32;
|
||||
|
||||
internal static partial class Interop
|
||||
{
|
||||
internal static partial class Libraries
|
||||
{
|
||||
internal const string Activeds = "activeds.dll";
|
||||
internal const string Advapi32 = "advapi32.dll";
|
||||
internal const string Authz = "authz.dll";
|
||||
internal const string BCrypt = "BCrypt.dll";
|
||||
internal const string Credui = "credui.dll";
|
||||
internal const string Crypt32 = "crypt32.dll";
|
||||
internal const string CryptUI = "cryptui.dll";
|
||||
internal const string Dnsapi = "dnsapi.dll";
|
||||
internal const string Dsrole = "dsrole.dll";
|
||||
internal const string Gdi32 = "gdi32.dll";
|
||||
internal const string HttpApi = "httpapi.dll";
|
||||
internal const string IpHlpApi = "iphlpapi.dll";
|
||||
internal const string Kernel32 = "kernel32.dll";
|
||||
internal const string Logoncli = "logoncli.dll";
|
||||
internal const string Mswsock = "mswsock.dll";
|
||||
internal const string NCrypt = "ncrypt.dll";
|
||||
internal const string Netapi32 = "netapi32.dll";
|
||||
internal const string Netutils = "netutils.dll";
|
||||
internal const string NtDll = "ntdll.dll";
|
||||
internal const string Odbc32 = "odbc32.dll";
|
||||
internal const string Ole32 = "ole32.dll";
|
||||
internal const string OleAut32 = "oleaut32.dll";
|
||||
internal const string Pdh = "pdh.dll";
|
||||
internal const string Secur32 = "secur32.dll";
|
||||
internal const string Shell32 = "shell32.dll";
|
||||
internal const string SspiCli = "sspicli.dll";
|
||||
internal const string User32 = "user32.dll";
|
||||
internal const string Version = "version.dll";
|
||||
internal const string WebSocket = "websocket.dll";
|
||||
internal const string Wevtapi = "wevtapi.dll";
|
||||
internal const string WinHttp = "winhttp.dll";
|
||||
internal const string WinMM = "winmm.dll";
|
||||
internal const string Wkscli = "wkscli.dll";
|
||||
internal const string Wldap32 = "wldap32.dll";
|
||||
internal const string Ws2_32 = "ws2_32.dll";
|
||||
internal const string Wtsapi32 = "wtsapi32.dll";
|
||||
internal const string CompressionNative = "System.IO.Compression.Native";
|
||||
internal const string GlobalizationNative = "System.Globalization.Native";
|
||||
internal const string MsQuic = "msquic.dll";
|
||||
internal const string HostPolicy = "hostpolicy";
|
||||
internal const string Ucrtbase = "ucrtbase.dll";
|
||||
internal const string Xolehlp = "xolehlp.dll";
|
||||
internal const string Comdlg32 = "comdlg32.dll";
|
||||
internal const string Gdiplus = "gdiplus.dll";
|
||||
internal const string Oleaut32 = "oleaut32.dll";
|
||||
internal const string Winspool = "winspool.drv";
|
||||
}
|
||||
}
|
||||
@@ -1,624 +0,0 @@
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.Marshalling;
|
||||
using System.Text;
|
||||
|
||||
namespace Snap.Hutao.Win32;
|
||||
|
||||
internal static partial class Interop
|
||||
{
|
||||
internal static partial class WinHttp
|
||||
{
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
public static partial SafeWinHttpHandle WinHttpOpen(
|
||||
IntPtr userAgent,
|
||||
uint accessType,
|
||||
string? proxyName,
|
||||
string? proxyBypass, int flags);
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool WinHttpCloseHandle(
|
||||
IntPtr handle);
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
public static partial SafeWinHttpHandle WinHttpConnect(
|
||||
SafeWinHttpHandle sessionHandle,
|
||||
string serverName,
|
||||
ushort serverPort,
|
||||
uint reserved);
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
public static partial SafeWinHttpHandle WinHttpOpenRequest(
|
||||
SafeWinHttpHandle connectHandle,
|
||||
string verb,
|
||||
string objectName,
|
||||
string? version,
|
||||
string referrer,
|
||||
string acceptTypes,
|
||||
uint flags);
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool WinHttpAddRequestHeaders(
|
||||
SafeWinHttpHandle requestHandle,
|
||||
#if NET7_0_OR_GREATER
|
||||
[MarshalUsing(typeof(SimpleStringBufferMarshaller))] StringBuilder headers,
|
||||
#else
|
||||
#pragma warning disable CA1838 // Uses pooled StringBuilder
|
||||
[In] StringBuilder headers,
|
||||
#pragma warning restore CA1838 // Uses pooled StringBuilder
|
||||
#endif
|
||||
uint headersLength,
|
||||
uint modifiers);
|
||||
|
||||
#if NET7_0_OR_GREATER
|
||||
[CustomMarshaller(typeof(StringBuilder), MarshalMode.ManagedToUnmanagedIn, typeof(SimpleStringBufferMarshaller))]
|
||||
private static unsafe class SimpleStringBufferMarshaller
|
||||
{
|
||||
public static void* ConvertToUnmanaged(StringBuilder builder)
|
||||
{
|
||||
int length = builder.Length + 1;
|
||||
void* value = NativeMemory.Alloc(sizeof(char) * (nuint)length);
|
||||
Span<char> buffer = new(value, length);
|
||||
buffer.Clear();
|
||||
builder.CopyTo(0, buffer, length - 1);
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void Free(void* value) => NativeMemory.Free(value);
|
||||
}
|
||||
#endif
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool WinHttpAddRequestHeaders(
|
||||
SafeWinHttpHandle requestHandle,
|
||||
string headers,
|
||||
uint headersLength,
|
||||
uint modifiers);
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool WinHttpSendRequest(
|
||||
SafeWinHttpHandle requestHandle,
|
||||
IntPtr headers,
|
||||
uint headersLength,
|
||||
IntPtr optional,
|
||||
uint optionalLength,
|
||||
uint totalLength,
|
||||
IntPtr context);
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool WinHttpReceiveResponse(
|
||||
SafeWinHttpHandle requestHandle,
|
||||
IntPtr reserved);
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool WinHttpQueryDataAvailable(
|
||||
SafeWinHttpHandle requestHandle,
|
||||
IntPtr parameterIgnoredAndShouldBeNullForAsync);
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool WinHttpReadData(
|
||||
SafeWinHttpHandle requestHandle,
|
||||
IntPtr buffer,
|
||||
uint bufferSize,
|
||||
IntPtr parameterIgnoredAndShouldBeNullForAsync);
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool WinHttpQueryHeaders(
|
||||
SafeWinHttpHandle requestHandle,
|
||||
uint infoLevel,
|
||||
string name,
|
||||
IntPtr buffer,
|
||||
ref uint bufferLength,
|
||||
ref uint index);
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool WinHttpQueryHeaders(
|
||||
SafeWinHttpHandle requestHandle,
|
||||
uint infoLevel,
|
||||
string name,
|
||||
ref uint number,
|
||||
ref uint bufferLength,
|
||||
IntPtr index);
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool WinHttpQueryOption(
|
||||
SafeWinHttpHandle handle,
|
||||
uint option,
|
||||
ref IntPtr buffer,
|
||||
ref uint bufferSize);
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool WinHttpQueryOption(
|
||||
SafeWinHttpHandle handle,
|
||||
uint option,
|
||||
IntPtr buffer,
|
||||
ref uint bufferSize);
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool WinHttpQueryOption(
|
||||
SafeWinHttpHandle handle,
|
||||
uint option,
|
||||
ref uint buffer,
|
||||
ref uint bufferSize);
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool WinHttpWriteData(
|
||||
SafeWinHttpHandle requestHandle,
|
||||
IntPtr buffer,
|
||||
uint bufferSize,
|
||||
IntPtr parameterIgnoredAndShouldBeNullForAsync);
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool WinHttpSetOption(
|
||||
SafeWinHttpHandle handle,
|
||||
uint option,
|
||||
ref uint optionData,
|
||||
uint optionLength = sizeof(uint));
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool WinHttpSetOption(
|
||||
SafeWinHttpHandle handle,
|
||||
uint option,
|
||||
IntPtr optionData,
|
||||
uint optionLength);
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool WinHttpSetCredentials(
|
||||
SafeWinHttpHandle requestHandle,
|
||||
uint authTargets,
|
||||
uint authScheme,
|
||||
string? userName,
|
||||
string? password,
|
||||
IntPtr reserved);
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool WinHttpQueryAuthSchemes(
|
||||
SafeWinHttpHandle requestHandle,
|
||||
out uint supportedSchemes,
|
||||
out uint firstScheme,
|
||||
out uint authTarget);
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool WinHttpSetTimeouts(
|
||||
SafeWinHttpHandle handle,
|
||||
int resolveTimeout,
|
||||
int connectTimeout,
|
||||
int sendTimeout,
|
||||
int receiveTimeout);
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool WinHttpGetIEProxyConfigForCurrentUser(
|
||||
out WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig);
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool WinHttpGetProxyForUrl(
|
||||
SafeWinHttpHandle? sessionHandle,
|
||||
string url,
|
||||
ref WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions,
|
||||
out WINHTTP_PROXY_INFO proxyInfo);
|
||||
|
||||
[LibraryImport(Libraries.WinHttp, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
public static partial IntPtr WinHttpSetStatusCallback(
|
||||
SafeWinHttpHandle handle,
|
||||
WINHTTP_STATUS_CALLBACK callback,
|
||||
uint notificationFlags,
|
||||
IntPtr reserved);
|
||||
}
|
||||
internal static partial class WinHttp
|
||||
{
|
||||
internal class SafeWinHttpHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
private SafeWinHttpHandle? _parentHandle;
|
||||
|
||||
public SafeWinHttpHandle() : base(true)
|
||||
{
|
||||
}
|
||||
|
||||
public static void DisposeAndClearHandle(ref SafeWinHttpHandle? safeHandle)
|
||||
{
|
||||
if (safeHandle != null)
|
||||
{
|
||||
safeHandle.Dispose();
|
||||
safeHandle = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetParentHandle(SafeWinHttpHandle parentHandle)
|
||||
{
|
||||
Debug.Assert(_parentHandle == null);
|
||||
Debug.Assert(parentHandle != null);
|
||||
Debug.Assert(!parentHandle.IsInvalid);
|
||||
|
||||
bool ignore = false;
|
||||
parentHandle.DangerousAddRef(ref ignore);
|
||||
|
||||
_parentHandle = parentHandle;
|
||||
}
|
||||
|
||||
// Important: WinHttp API calls should not happen while another WinHttp call for the same handle did not
|
||||
// return. During finalization that was not initiated by the Dispose pattern we don't expect any other WinHttp
|
||||
// calls in progress.
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
if (_parentHandle != null)
|
||||
{
|
||||
_parentHandle.DangerousRelease();
|
||||
_parentHandle = null;
|
||||
}
|
||||
|
||||
return Interop.WinHttp.WinHttpCloseHandle(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
internal static partial class WinHttp
|
||||
{
|
||||
public const uint ERROR_SUCCESS = 0;
|
||||
public const uint ERROR_FILE_NOT_FOUND = 2;
|
||||
public const uint ERROR_INVALID_HANDLE = 6;
|
||||
public const uint ERROR_INVALID_PARAMETER = 87;
|
||||
public const uint ERROR_INSUFFICIENT_BUFFER = 122;
|
||||
public const uint ERROR_NOT_FOUND = 1168;
|
||||
public const uint ERROR_WINHTTP_INVALID_OPTION = 12009;
|
||||
public const uint ERROR_WINHTTP_LOGIN_FAILURE = 12015;
|
||||
public const uint ERROR_WINHTTP_OPERATION_CANCELLED = 12017;
|
||||
public const uint ERROR_WINHTTP_INCORRECT_HANDLE_STATE = 12019;
|
||||
public const uint ERROR_WINHTTP_CONNECTION_ERROR = 12030;
|
||||
public const uint ERROR_WINHTTP_RESEND_REQUEST = 12032;
|
||||
public const uint ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED = 12044;
|
||||
public const uint ERROR_WINHTTP_HEADER_NOT_FOUND = 12150;
|
||||
public const uint ERROR_WINHTTP_SECURE_FAILURE = 12175;
|
||||
public const uint ERROR_WINHTTP_AUTODETECTION_FAILED = 12180;
|
||||
|
||||
public const uint WINHTTP_OPTION_PROXY = 38;
|
||||
public const uint WINHTTP_ACCESS_TYPE_DEFAULT_PROXY = 0;
|
||||
public const uint WINHTTP_ACCESS_TYPE_NO_PROXY = 1;
|
||||
public const uint WINHTTP_ACCESS_TYPE_NAMED_PROXY = 3;
|
||||
public const uint WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY = 4;
|
||||
|
||||
public const uint WINHTTP_AUTOPROXY_AUTO_DETECT = 0x00000001;
|
||||
public const uint WINHTTP_AUTOPROXY_CONFIG_URL = 0x00000002;
|
||||
public const uint WINHTTP_AUTOPROXY_HOST_KEEPCASE = 0x00000004;
|
||||
public const uint WINHTTP_AUTOPROXY_HOST_LOWERCASE = 0x00000008;
|
||||
public const uint WINHTTP_AUTOPROXY_RUN_INPROCESS = 0x00010000;
|
||||
public const uint WINHTTP_AUTOPROXY_RUN_OUTPROCESS_ONLY = 0x00020000;
|
||||
public const uint WINHTTP_AUTOPROXY_NO_DIRECTACCESS = 0x00040000;
|
||||
public const uint WINHTTP_AUTOPROXY_NO_CACHE_CLIENT = 0x00080000;
|
||||
public const uint WINHTTP_AUTOPROXY_NO_CACHE_SVC = 0x00100000;
|
||||
public const uint WINHTTP_AUTOPROXY_SORT_RESULTS = 0x00400000;
|
||||
|
||||
public const uint WINHTTP_AUTO_DETECT_TYPE_DHCP = 0x00000001;
|
||||
public const uint WINHTTP_AUTO_DETECT_TYPE_DNS_A = 0x00000002;
|
||||
|
||||
public const string WINHTTP_NO_PROXY_NAME = null;
|
||||
public const string WINHTTP_NO_PROXY_BYPASS = null;
|
||||
|
||||
public const uint WINHTTP_ADDREQ_FLAG_ADD = 0x20000000;
|
||||
public const uint WINHTTP_ADDREQ_FLAG_REPLACE = 0x80000000;
|
||||
|
||||
public const string WINHTTP_NO_REFERER = null;
|
||||
public const string WINHTTP_DEFAULT_ACCEPT_TYPES = null;
|
||||
|
||||
public const ushort INTERNET_DEFAULT_PORT = 0;
|
||||
public const ushort INTERNET_DEFAULT_HTTP_PORT = 80;
|
||||
public const ushort INTERNET_DEFAULT_HTTPS_PORT = 443;
|
||||
|
||||
public const uint WINHTTP_FLAG_SECURE = 0x00800000;
|
||||
public const uint WINHTTP_FLAG_ESCAPE_DISABLE = 0x00000040;
|
||||
public const uint WINHTTP_FLAG_AUTOMATIC_CHUNKING = 0x00000200;
|
||||
|
||||
public const uint WINHTTP_QUERY_FLAG_NUMBER = 0x20000000;
|
||||
public const uint WINHTTP_QUERY_VERSION = 18;
|
||||
public const uint WINHTTP_QUERY_STATUS_CODE = 19;
|
||||
public const uint WINHTTP_QUERY_STATUS_TEXT = 20;
|
||||
public const uint WINHTTP_QUERY_RAW_HEADERS = 21;
|
||||
public const uint WINHTTP_QUERY_RAW_HEADERS_CRLF = 22;
|
||||
public const uint WINHTTP_QUERY_FLAG_TRAILERS = 0x02000000;
|
||||
public const uint WINHTTP_QUERY_CONTENT_ENCODING = 29;
|
||||
public const uint WINHTTP_QUERY_SET_COOKIE = 43;
|
||||
public const uint WINHTTP_QUERY_CUSTOM = 65535;
|
||||
public const string WINHTTP_HEADER_NAME_BY_INDEX = null;
|
||||
public const byte[] WINHTTP_NO_OUTPUT_BUFFER = null;
|
||||
|
||||
public const uint WINHTTP_OPTION_DECOMPRESSION = 118;
|
||||
public const uint WINHTTP_DECOMPRESSION_FLAG_GZIP = 0x00000001;
|
||||
public const uint WINHTTP_DECOMPRESSION_FLAG_DEFLATE = 0x00000002;
|
||||
public const uint WINHTTP_DECOMPRESSION_FLAG_ALL = WINHTTP_DECOMPRESSION_FLAG_GZIP | WINHTTP_DECOMPRESSION_FLAG_DEFLATE;
|
||||
|
||||
public const uint WINHTTP_OPTION_REDIRECT_POLICY = 88;
|
||||
public const uint WINHTTP_OPTION_REDIRECT_POLICY_NEVER = 0;
|
||||
public const uint WINHTTP_OPTION_REDIRECT_POLICY_DISALLOW_HTTPS_TO_HTTP = 1;
|
||||
public const uint WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS = 2;
|
||||
public const uint WINHTTP_OPTION_MAX_HTTP_AUTOMATIC_REDIRECTS = 89;
|
||||
|
||||
public const uint WINHTTP_OPTION_MAX_CONNS_PER_SERVER = 73;
|
||||
public const uint WINHTTP_OPTION_MAX_CONNS_PER_1_0_SERVER = 74;
|
||||
|
||||
public const uint WINHTTP_OPTION_DISABLE_FEATURE = 63;
|
||||
public const uint WINHTTP_DISABLE_COOKIES = 0x00000001;
|
||||
public const uint WINHTTP_DISABLE_REDIRECTS = 0x00000002;
|
||||
public const uint WINHTTP_DISABLE_AUTHENTICATION = 0x00000004;
|
||||
public const uint WINHTTP_DISABLE_KEEP_ALIVE = 0x00000008;
|
||||
|
||||
public const uint WINHTTP_OPTION_ENABLE_FEATURE = 79;
|
||||
public const uint WINHTTP_ENABLE_SSL_REVOCATION = 0x00000001;
|
||||
|
||||
public const uint WINHTTP_OPTION_CLIENT_CERT_CONTEXT = 47;
|
||||
public const uint WINHTTP_OPTION_CLIENT_CERT_ISSUER_LIST = 94;
|
||||
public const uint WINHTTP_OPTION_SERVER_CERT_CONTEXT = 78;
|
||||
public const uint WINHTTP_OPTION_SECURITY_FLAGS = 31;
|
||||
public const uint WINHTTP_OPTION_SECURE_PROTOCOLS = 84;
|
||||
public const uint WINHTTP_FLAG_SECURE_PROTOCOL_SSL2 = 0x00000008;
|
||||
public const uint WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 = 0x00000020;
|
||||
public const uint WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 = 0x00000080;
|
||||
public const uint WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 = 0x00000200;
|
||||
public const uint WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 = 0x00000800;
|
||||
public const uint WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3 = 0x00002000;
|
||||
|
||||
public const uint SECURITY_FLAG_IGNORE_UNKNOWN_CA = 0x00000100;
|
||||
public const uint SECURITY_FLAG_IGNORE_CERT_DATE_INVALID = 0x00002000;
|
||||
public const uint SECURITY_FLAG_IGNORE_CERT_CN_INVALID = 0x00001000;
|
||||
public const uint SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE = 0x00000200;
|
||||
|
||||
public const uint WINHTTP_OPTION_AUTOLOGON_POLICY = 77;
|
||||
public const uint WINHTTP_AUTOLOGON_SECURITY_LEVEL_MEDIUM = 0; // default creds only sent to intranet servers (default)
|
||||
public const uint WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW = 1; // default creds set to all servers
|
||||
public const uint WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH = 2; // default creds never sent
|
||||
|
||||
public const uint WINHTTP_AUTH_SCHEME_BASIC = 0x00000001;
|
||||
public const uint WINHTTP_AUTH_SCHEME_NTLM = 0x00000002;
|
||||
public const uint WINHTTP_AUTH_SCHEME_PASSPORT = 0x00000004;
|
||||
public const uint WINHTTP_AUTH_SCHEME_DIGEST = 0x00000008;
|
||||
public const uint WINHTTP_AUTH_SCHEME_NEGOTIATE = 0x00000010;
|
||||
|
||||
public const uint WINHTTP_AUTH_TARGET_SERVER = 0x00000000;
|
||||
public const uint WINHTTP_AUTH_TARGET_PROXY = 0x00000001;
|
||||
|
||||
public const uint WINHTTP_OPTION_USERNAME = 0x1000;
|
||||
// [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Suppression approved. It is property descriptor, not secret value.")]
|
||||
public const uint WINHTTP_OPTION_PASSWORD = 0x1001;
|
||||
public const uint WINHTTP_OPTION_PROXY_USERNAME = 0x1002;
|
||||
// [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Suppression approved. It is property descriptor, not secret value.")]
|
||||
public const uint WINHTTP_OPTION_PROXY_PASSWORD = 0x1003;
|
||||
|
||||
public const uint WINHTTP_OPTION_SERVER_SPN_USED = 106;
|
||||
public const uint WINHTTP_OPTION_SERVER_CBT = 108;
|
||||
|
||||
public const uint WINHTTP_OPTION_CONNECT_TIMEOUT = 3;
|
||||
public const uint WINHTTP_OPTION_SEND_TIMEOUT = 5;
|
||||
public const uint WINHTTP_OPTION_RECEIVE_TIMEOUT = 6;
|
||||
|
||||
public const uint WINHTTP_OPTION_URL = 34;
|
||||
|
||||
public const uint WINHTTP_OPTION_MAX_RESPONSE_HEADER_SIZE = 91;
|
||||
public const uint WINHTTP_OPTION_MAX_RESPONSE_DRAIN_SIZE = 92;
|
||||
public const uint WINHTTP_OPTION_CONNECTION_INFO = 93;
|
||||
|
||||
public const uint WINHTTP_OPTION_ASSURED_NON_BLOCKING_CALLBACKS = 111;
|
||||
|
||||
public const uint WINHTTP_OPTION_ENABLE_HTTP2_PLUS_CLIENT_CERT = 161;
|
||||
public const uint WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL = 133;
|
||||
public const uint WINHTTP_OPTION_HTTP_PROTOCOL_USED = 134;
|
||||
public const uint WINHTTP_PROTOCOL_FLAG_HTTP2 = 0x1;
|
||||
public const uint WINHTTP_HTTP2_PLUS_CLIENT_CERT_FLAG = 0x1;
|
||||
public const uint WINHTTP_OPTION_DISABLE_STREAM_QUEUE = 139;
|
||||
|
||||
public const uint WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET = 114;
|
||||
public const uint WINHTTP_OPTION_WEB_SOCKET_CLOSE_TIMEOUT = 115;
|
||||
public const uint WINHTTP_OPTION_WEB_SOCKET_KEEPALIVE_INTERVAL = 116;
|
||||
|
||||
public const uint WINHTTP_OPTION_WEB_SOCKET_RECEIVE_BUFFER_SIZE = 122;
|
||||
public const uint WINHTTP_OPTION_WEB_SOCKET_SEND_BUFFER_SIZE = 123;
|
||||
|
||||
public const uint WINHTTP_OPTION_TCP_KEEPALIVE = 152;
|
||||
public const uint WINHTTP_OPTION_STREAM_ERROR_CODE = 159;
|
||||
public const uint WINHTTP_OPTION_REQUIRE_STREAM_END = 160;
|
||||
|
||||
public enum WINHTTP_WEB_SOCKET_BUFFER_TYPE
|
||||
{
|
||||
WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE = 0,
|
||||
WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE = 1,
|
||||
WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE = 2,
|
||||
WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE = 3,
|
||||
WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE = 4
|
||||
}
|
||||
|
||||
public const uint WINHTTP_OPTION_CONTEXT_VALUE = 45;
|
||||
|
||||
public const uint WINHTTP_FLAG_ASYNC = 0x10000000;
|
||||
|
||||
public const uint WINHTTP_CALLBACK_STATUS_RESOLVING_NAME = 0x00000001;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_NAME_RESOLVED = 0x00000002;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER = 0x00000004;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER = 0x00000008;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_SENDING_REQUEST = 0x00000010;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_REQUEST_SENT = 0x00000020;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE = 0x00000040;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED = 0x00000080;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION = 0x00000100;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED = 0x00000200;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_HANDLE_CREATED = 0x00000400;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING = 0x00000800;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_DETECTING_PROXY = 0x00001000;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_REDIRECT = 0x00004000;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE = 0x00008000;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_SECURE_FAILURE = 0x00010000;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE = 0x00020000;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE = 0x00040000;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_READ_COMPLETE = 0x00080000;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE = 0x00100000;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_REQUEST_ERROR = 0x00200000;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE = 0x00400000;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE = 0x01000000;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_CLOSE_COMPLETE = 0x02000000;
|
||||
public const uint WINHTTP_CALLBACK_STATUS_SHUTDOWN_COMPLETE = 0x04000000;
|
||||
|
||||
public const uint WINHTTP_CALLBACK_FLAG_SEND_REQUEST =
|
||||
WINHTTP_CALLBACK_STATUS_SENDING_REQUEST |
|
||||
WINHTTP_CALLBACK_STATUS_REQUEST_SENT;
|
||||
public const uint WINHTTP_CALLBACK_FLAG_HANDLES =
|
||||
WINHTTP_CALLBACK_STATUS_HANDLE_CREATED |
|
||||
WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING;
|
||||
public const uint WINHTTP_CALLBACK_FLAG_REDIRECT = WINHTTP_CALLBACK_STATUS_REDIRECT;
|
||||
public const uint WINHTTP_CALLBACK_FLAG_SECURE_FAILURE = WINHTTP_CALLBACK_STATUS_SECURE_FAILURE;
|
||||
public const uint WINHTTP_CALLBACK_FLAG_SENDREQUEST_COMPLETE = WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE;
|
||||
public const uint WINHTTP_CALLBACK_FLAG_HEADERS_AVAILABLE = WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE;
|
||||
public const uint WINHTTP_CALLBACK_FLAG_DATA_AVAILABLE = WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE;
|
||||
public const uint WINHTTP_CALLBACK_FLAG_READ_COMPLETE = WINHTTP_CALLBACK_STATUS_READ_COMPLETE;
|
||||
public const uint WINHTTP_CALLBACK_FLAG_WRITE_COMPLETE = WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE;
|
||||
public const uint WINHTTP_CALLBACK_FLAG_REQUEST_ERROR = WINHTTP_CALLBACK_STATUS_REQUEST_ERROR;
|
||||
public const uint WINHTTP_CALLBACK_FLAG_GETPROXYFORURL_COMPLETE = WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE;
|
||||
public const uint WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS =
|
||||
WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE |
|
||||
WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE |
|
||||
WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE |
|
||||
WINHTTP_CALLBACK_STATUS_READ_COMPLETE |
|
||||
WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE |
|
||||
WINHTTP_CALLBACK_STATUS_REQUEST_ERROR |
|
||||
WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE;
|
||||
public const uint WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS = 0xFFFFFFFF;
|
||||
|
||||
public const uint WININET_E_CONNECTION_RESET = 0x80072EFF;
|
||||
|
||||
public const int WINHTTP_INVALID_STATUS_CALLBACK = -1;
|
||||
public delegate void WINHTTP_STATUS_CALLBACK(
|
||||
IntPtr handle,
|
||||
IntPtr context,
|
||||
uint internetStatus,
|
||||
IntPtr statusInformation,
|
||||
uint statusInformationLength);
|
||||
|
||||
#if NET7_0_OR_GREATER
|
||||
[NativeMarshalling(typeof(Marshaller))]
|
||||
#endif
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct WINHTTP_AUTOPROXY_OPTIONS
|
||||
{
|
||||
public uint Flags;
|
||||
public uint AutoDetectFlags;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string? AutoConfigUrl;
|
||||
public IntPtr Reserved1;
|
||||
public uint Reserved2;
|
||||
[MarshalAs(UnmanagedType.Bool)]
|
||||
public bool AutoLoginIfChallenged;
|
||||
#if NET7_0_OR_GREATER
|
||||
[CustomMarshaller(typeof(WINHTTP_AUTOPROXY_OPTIONS), MarshalMode.Default, typeof(Marshaller))]
|
||||
public static class Marshaller
|
||||
{
|
||||
public static Native ConvertToUnmanaged(WINHTTP_AUTOPROXY_OPTIONS managed) => new(managed);
|
||||
|
||||
public static WINHTTP_AUTOPROXY_OPTIONS ConvertToManaged(Native native) => native.ToManaged();
|
||||
|
||||
public static void Free(Native native) => native.FreeNative();
|
||||
|
||||
public readonly struct Native
|
||||
{
|
||||
private readonly uint Flags;
|
||||
private readonly uint AutoDetectFlags;
|
||||
private readonly IntPtr AutoConfigUrl;
|
||||
private readonly IntPtr Reserved1;
|
||||
private readonly uint Reserved2;
|
||||
private readonly int AutoLoginIfChallenged;
|
||||
|
||||
public Native(WINHTTP_AUTOPROXY_OPTIONS managed)
|
||||
{
|
||||
Flags = managed.Flags;
|
||||
AutoDetectFlags = managed.AutoDetectFlags;
|
||||
AutoConfigUrl = managed.AutoConfigUrl is not null ? Marshal.StringToCoTaskMemUni(managed.AutoConfigUrl) : IntPtr.Zero;
|
||||
Reserved1 = managed.Reserved1;
|
||||
Reserved2 = managed.Reserved2;
|
||||
AutoLoginIfChallenged = managed.AutoLoginIfChallenged ? 1 : 0;
|
||||
}
|
||||
|
||||
public WINHTTP_AUTOPROXY_OPTIONS ToManaged()
|
||||
{
|
||||
return new WINHTTP_AUTOPROXY_OPTIONS
|
||||
{
|
||||
Flags = Flags,
|
||||
AutoDetectFlags = AutoDetectFlags,
|
||||
AutoConfigUrl = AutoConfigUrl != IntPtr.Zero ? Marshal.PtrToStringUni(AutoConfigUrl) : null,
|
||||
Reserved1 = Reserved1,
|
||||
Reserved2 = Reserved2,
|
||||
AutoLoginIfChallenged = AutoLoginIfChallenged != 0
|
||||
};
|
||||
}
|
||||
|
||||
public void FreeNative()
|
||||
{
|
||||
Marshal.FreeCoTaskMem(AutoConfigUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct WINHTTP_CURRENT_USER_IE_PROXY_CONFIG
|
||||
{
|
||||
public int AutoDetect;
|
||||
public IntPtr AutoConfigUrl;
|
||||
public IntPtr Proxy;
|
||||
public IntPtr ProxyBypass;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct WINHTTP_PROXY_INFO
|
||||
{
|
||||
public uint AccessType;
|
||||
public IntPtr Proxy;
|
||||
public IntPtr ProxyBypass;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct WINHTTP_ASYNC_RESULT
|
||||
{
|
||||
public IntPtr dwResult;
|
||||
public uint dwError;
|
||||
}
|
||||
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct tcp_keepalive
|
||||
{
|
||||
public uint onoff;
|
||||
public uint keepalivetime;
|
||||
public uint keepaliveinterval;
|
||||
}
|
||||
|
||||
public const uint API_RECEIVE_RESPONSE = 1;
|
||||
public const uint API_QUERY_DATA_AVAILABLE = 2;
|
||||
public const uint API_READ_DATA = 3;
|
||||
public const uint API_WRITE_DATA = 4;
|
||||
public const uint API_SEND_REQUEST = 5;
|
||||
|
||||
public enum WINHTTP_WEB_SOCKET_OPERATION
|
||||
{
|
||||
WINHTTP_WEB_SOCKET_SEND_OPERATION = 0,
|
||||
WINHTTP_WEB_SOCKET_RECEIVE_OPERATION = 1,
|
||||
WINHTTP_WEB_SOCKET_CLOSE_OPERATION = 2,
|
||||
WINHTTP_WEB_SOCKET_SHUTDOWN_OPERATION = 3
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,23 +3,23 @@
|
||||
|
||||
using Snap.Hutao.Core.Shell;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Http.DynamicProxy;
|
||||
|
||||
[Injection(InjectAs.Singleton)]
|
||||
internal sealed partial class DynamicHttpProxy : IWebProxy, IDisposable
|
||||
{
|
||||
private readonly RegistryMonitor registryMonitor;
|
||||
private readonly RegistryWatcher watcher;
|
||||
|
||||
private IWebProxy innerProxy;
|
||||
private IWebProxy innerProxy = default!;
|
||||
|
||||
public DynamicHttpProxy()
|
||||
{
|
||||
HttpWindowsProxy.TryCreate(out IWebProxy? proxy);
|
||||
innerProxy = proxy ?? new HttpNoProxy();
|
||||
UpdateProxy();
|
||||
|
||||
registryMonitor = RegistryMonitor.Create(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Connections", OnRegistryChanged);
|
||||
registryMonitor.Start();
|
||||
watcher = RegistryWatcher.Create(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Connections", OnRegistryChanged);
|
||||
watcher.Start();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -50,8 +50,14 @@ internal sealed partial class DynamicHttpProxy : IWebProxy, IDisposable
|
||||
|
||||
public void UpdateProxy()
|
||||
{
|
||||
HttpWindowsProxy.TryCreate(out IWebProxy? proxy);
|
||||
InnerProxy = proxy ?? new HttpNoProxy();
|
||||
Assembly httpNamespace = Assembly.Load("System.Net.Http");
|
||||
Type? systemProxyInfoType = httpNamespace.GetType("System.Net.Http.SystemProxyInfo");
|
||||
ArgumentNullException.ThrowIfNull(systemProxyInfoType);
|
||||
MethodInfo? constructSystemProxyMethod = systemProxyInfoType.GetMethod("ConstructSystemProxy", BindingFlags.Static | BindingFlags.Public);
|
||||
ArgumentNullException.ThrowIfNull(constructSystemProxyMethod);
|
||||
IWebProxy? proxy = (IWebProxy?)constructSystemProxyMethod.Invoke(null, null);
|
||||
ArgumentNullException.ThrowIfNull(proxy);
|
||||
InnerProxy = proxy;
|
||||
}
|
||||
|
||||
public Uri? GetProxy(Uri destination)
|
||||
@@ -71,7 +77,7 @@ internal sealed partial class DynamicHttpProxy : IWebProxy, IDisposable
|
||||
disposable.Dispose();
|
||||
}
|
||||
|
||||
registryMonitor.Dispose();
|
||||
watcher.Dispose();
|
||||
}
|
||||
|
||||
private void OnRegistryChanged(object? sender, EventArgs e)
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
#pragma warning disable
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Http.DynamicProxy;
|
||||
|
||||
/// <summary>
|
||||
/// Copied from https://github.com/dotnet/runtime/blob/v8.0.0/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/FailedProxyCache.cs
|
||||
/// </summary>
|
||||
internal sealed class FailedProxyCache
|
||||
{
|
||||
/// <summary>
|
||||
/// When returned by <see cref="GetProxyRenewTicks"/>, indicates a proxy is immediately usable.
|
||||
/// </summary>
|
||||
public const long Immediate = 0;
|
||||
|
||||
// If a proxy fails, time out 30 minutes. WinHTTP and Firefox both use this.
|
||||
private const int FailureTimeoutInMilliseconds = 1000 * 60 * 30;
|
||||
|
||||
// Scan through the failures and flush any that have expired every 5 minutes.
|
||||
private const int FlushFailuresTimerInMilliseconds = 1000 * 60 * 5;
|
||||
|
||||
// _failedProxies will only be flushed (rare but somewhat expensive) if we have more than this number of proxies in our dictionary. See Cleanup() for details.
|
||||
private const int LargeProxyConfigBoundary = 8;
|
||||
|
||||
// Value is the Environment.TickCount64 to remove the proxy from the failure list.
|
||||
private readonly ConcurrentDictionary<Uri, long> _failedProxies = new ConcurrentDictionary<Uri, long>();
|
||||
|
||||
// When Environment.TickCount64 >= _nextFlushTicks, cause a flush.
|
||||
private long _nextFlushTicks = Environment.TickCount64 + FlushFailuresTimerInMilliseconds;
|
||||
|
||||
// This lock can be folded into _nextFlushTicks for space optimization, but
|
||||
// this class should only have a single instance so would rather have clarity.
|
||||
private SpinLock _flushLock = new SpinLock(enableThreadOwnerTracking: false); // mutable struct; do not make this readonly
|
||||
|
||||
/// <summary>
|
||||
/// Checks when a proxy will become usable.
|
||||
/// </summary>
|
||||
/// <param name="uri">The <see cref="Uri"/> of the proxy to check.</param>
|
||||
/// <returns>If the proxy can be used, <see cref="Immediate"/>. Otherwise, the next <see cref="Environment.TickCount64"/> that it should be used.</returns>
|
||||
public long GetProxyRenewTicks(Uri uri)
|
||||
{
|
||||
Cleanup();
|
||||
|
||||
// If not failed, ready immediately.
|
||||
if (!_failedProxies.TryGetValue(uri, out long renewTicks))
|
||||
{
|
||||
return Immediate;
|
||||
}
|
||||
|
||||
// If we haven't reached out renew time, the proxy can't be used.
|
||||
if (Environment.TickCount64 < renewTicks)
|
||||
{
|
||||
return renewTicks;
|
||||
}
|
||||
|
||||
// Renew time reached, we can remove the proxy from the cache.
|
||||
if (TryRenewProxy(uri, renewTicks))
|
||||
{
|
||||
return Immediate;
|
||||
}
|
||||
|
||||
// Another thread updated the cache before we could remove it.
|
||||
// We can't know if this is a removal or an update, so check again.
|
||||
return _failedProxies.TryGetValue(uri, out renewTicks) ? renewTicks : Immediate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a proxy as failed, to avoid trying it again for some time.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI of the proxy.</param>
|
||||
public void SetProxyFailed(Uri uri)
|
||||
{
|
||||
_failedProxies[uri] = Environment.TickCount64 + FailureTimeoutInMilliseconds;
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renews a proxy prior to its period expiring. Used when all proxies are failed to renew the proxy closest to being renewed.
|
||||
/// </summary>
|
||||
/// <param name="uri">The <paramref name="uri"/> of the proxy to renew.</param>
|
||||
/// <param name="renewTicks">The current renewal time for the proxy. If the value has changed from this, the proxy will not be renewed.</param>
|
||||
public bool TryRenewProxy(Uri uri, long renewTicks) =>
|
||||
_failedProxies.TryRemove(new KeyValuePair<Uri, long>(uri, renewTicks));
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up any old proxies that should no longer be marked as failing.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void Cleanup()
|
||||
{
|
||||
if (_failedProxies.Count > LargeProxyConfigBoundary && Environment.TickCount64 >= Interlocked.Read(ref _nextFlushTicks))
|
||||
{
|
||||
CleanupHelper();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up any old proxies that should no longer be marked as failing.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// I expect this to never be called by <see cref="Cleanup"/> in a production system. It is only needed in the case
|
||||
/// that a system has a very large number of proxies that the PAC script cycles through. It is moderately expensive,
|
||||
/// so it's only run periodically and is disabled until we exceed <see cref="LargeProxyConfigBoundary"/> failed proxies.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void CleanupHelper()
|
||||
{
|
||||
bool lockTaken = false;
|
||||
try
|
||||
{
|
||||
_flushLock.TryEnter(ref lockTaken);
|
||||
if (!lockTaken)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
long curTicks = Environment.TickCount64;
|
||||
|
||||
foreach (KeyValuePair<Uri, long> kvp in _failedProxies)
|
||||
{
|
||||
if (curTicks >= kvp.Value)
|
||||
{
|
||||
((ICollection<KeyValuePair<Uri, long>>)_failedProxies).Remove(kvp);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken)
|
||||
{
|
||||
Interlocked.Exchange(ref _nextFlushTicks, Environment.TickCount64 + FlushFailuresTimerInMilliseconds);
|
||||
_flushLock.Exit(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore
|
||||
@@ -1,15 +0,0 @@
|
||||
#pragma warning disable
|
||||
using System.Net;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Http.DynamicProxy;
|
||||
|
||||
/// <summary>
|
||||
/// Copied from https://github.com/dotnet/runtime/blob/v8.0.0/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpNoProxy.cs
|
||||
/// </summary>
|
||||
internal sealed class HttpNoProxy : IWebProxy
|
||||
{
|
||||
public ICredentials? Credentials { get; set; }
|
||||
public Uri? GetProxy(Uri destination) => null;
|
||||
public bool IsBypassed(Uri host) => true;
|
||||
}
|
||||
#pragma warning restore
|
||||
@@ -1,325 +0,0 @@
|
||||
#pragma warning disable
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Snap.Hutao.Win32.Interop.WinHttp;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Http.DynamicProxy;
|
||||
|
||||
/// <summary>
|
||||
/// Copied from https://github.com/dotnet/runtime/blob/v8.0.0/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpWindowsProxy.cs
|
||||
/// </summary>
|
||||
internal sealed class HttpWindowsProxy : IMultiWebProxy, IDisposable
|
||||
{
|
||||
private readonly MultiProxy _insecureProxy; // URI of the http system proxy if set
|
||||
private readonly MultiProxy _secureProxy; // URI of the https system proxy if set
|
||||
private readonly FailedProxyCache _failedProxies = new FailedProxyCache();
|
||||
private readonly List<string>? _bypass; // list of domains not to proxy
|
||||
private readonly bool _bypassLocal; // we should bypass domain considered local
|
||||
private readonly List<IPAddress>? _localIp;
|
||||
private ICredentials? _credentials;
|
||||
private readonly WinInetProxyHelper _proxyHelper;
|
||||
private SafeWinHttpHandle? _sessionHandle;
|
||||
private bool _disposed;
|
||||
|
||||
public static bool TryCreate([NotNullWhen(true)] out IWebProxy? proxy)
|
||||
{
|
||||
// This will get basic proxy setting from system using existing
|
||||
// WinInetProxyHelper functions. If no proxy is enabled, it will return null.
|
||||
SafeWinHttpHandle? sessionHandle = null;
|
||||
proxy = null;
|
||||
|
||||
WinInetProxyHelper proxyHelper = new WinInetProxyHelper();
|
||||
if (!proxyHelper.ManualSettingsOnly && !proxyHelper.AutoSettingsUsed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (proxyHelper.AutoSettingsUsed)
|
||||
{
|
||||
sessionHandle = WinHttpOpen(
|
||||
IntPtr.Zero,
|
||||
WINHTTP_ACCESS_TYPE_NO_PROXY,
|
||||
WINHTTP_NO_PROXY_NAME,
|
||||
WINHTTP_NO_PROXY_BYPASS,
|
||||
(int)WINHTTP_FLAG_ASYNC);
|
||||
|
||||
if (sessionHandle.IsInvalid)
|
||||
{
|
||||
// Proxy failures are currently ignored by managed handler.
|
||||
sessionHandle.Dispose();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
proxy = new HttpWindowsProxy(proxyHelper, sessionHandle);
|
||||
return true;
|
||||
}
|
||||
|
||||
private HttpWindowsProxy(WinInetProxyHelper proxyHelper, SafeWinHttpHandle? sessionHandle)
|
||||
{
|
||||
_proxyHelper = proxyHelper;
|
||||
_sessionHandle = sessionHandle;
|
||||
|
||||
if (proxyHelper.ManualSettingsUsed)
|
||||
{
|
||||
_secureProxy = MultiProxy.Parse(_failedProxies, proxyHelper.Proxy, true);
|
||||
_insecureProxy = MultiProxy.Parse(_failedProxies, proxyHelper.Proxy, false);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(proxyHelper.ProxyBypass))
|
||||
{
|
||||
int idx = 0;
|
||||
string? tmp;
|
||||
|
||||
// Process bypass list for manual setting.
|
||||
// Initial list size is best guess based on string length assuming each entry is at least 5 characters on average.
|
||||
_bypass = new List<string>(proxyHelper.ProxyBypass.Length / 5);
|
||||
|
||||
while (idx < proxyHelper.ProxyBypass.Length)
|
||||
{
|
||||
// Strip leading spaces and scheme if any.
|
||||
while (idx < proxyHelper.ProxyBypass.Length && proxyHelper.ProxyBypass[idx] == ' ') { idx += 1; };
|
||||
if (string.Compare(proxyHelper.ProxyBypass, idx, "http://", 0, 7, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
idx += 7;
|
||||
}
|
||||
else if (string.Compare(proxyHelper.ProxyBypass, idx, "https://", 0, 8, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
idx += 8;
|
||||
}
|
||||
|
||||
if (idx < proxyHelper.ProxyBypass.Length && proxyHelper.ProxyBypass[idx] == '[')
|
||||
{
|
||||
// Strip [] from IPv6 so we can use IdnHost laster for matching.
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
int start = idx;
|
||||
while (idx < proxyHelper.ProxyBypass.Length && proxyHelper.ProxyBypass[idx] != ' ' && proxyHelper.ProxyBypass[idx] != ';' && proxyHelper.ProxyBypass[idx] != ']') { idx += 1; };
|
||||
|
||||
if (idx == start)
|
||||
{
|
||||
// Empty string.
|
||||
tmp = null;
|
||||
}
|
||||
else if (string.Compare(proxyHelper.ProxyBypass, start, "<local>", 0, 7, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
_bypassLocal = true;
|
||||
tmp = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
tmp = proxyHelper.ProxyBypass.Substring(start, idx - start);
|
||||
}
|
||||
|
||||
// Skip trailing characters if any.
|
||||
if (idx < proxyHelper.ProxyBypass.Length && proxyHelper.ProxyBypass[idx] != ';')
|
||||
{
|
||||
// Got stopped at space or ']'. Strip until next ';' or end.
|
||||
while (idx < proxyHelper.ProxyBypass.Length && proxyHelper.ProxyBypass[idx] != ';') { idx += 1; };
|
||||
}
|
||||
if (idx < proxyHelper.ProxyBypass.Length && proxyHelper.ProxyBypass[idx] == ';')
|
||||
{
|
||||
idx++;
|
||||
}
|
||||
if (tmp == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_bypass.Add(tmp);
|
||||
}
|
||||
if (_bypass.Count == 0)
|
||||
{
|
||||
// Bypass string only had garbage we did not parse.
|
||||
_bypass = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (_bypassLocal)
|
||||
{
|
||||
_localIp = new List<IPAddress>();
|
||||
foreach (NetworkInterface netInterface in NetworkInterface.GetAllNetworkInterfaces())
|
||||
{
|
||||
IPInterfaceProperties ipProps = netInterface.GetIPProperties();
|
||||
foreach (UnicastIPAddressInformation addr in ipProps.UnicastAddresses)
|
||||
{
|
||||
_localIp.Add(addr.Address);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
if (_sessionHandle != null && !_sessionHandle.IsInvalid)
|
||||
{
|
||||
SafeWinHttpHandle.DisposeAndClearHandle(ref _sessionHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the proxy URI. (IWebProxy interface)
|
||||
/// </summary>
|
||||
public Uri? GetProxy(Uri uri)
|
||||
{
|
||||
GetMultiProxy(uri).ReadNext(out Uri? proxyUri, out _);
|
||||
return proxyUri;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the proxy URIs.
|
||||
/// </summary>
|
||||
public MultiProxy GetMultiProxy(Uri uri)
|
||||
{
|
||||
// We need WinHTTP to detect and/or process a PAC (JavaScript) file. This maps to
|
||||
// "Automatically detect settings" and/or "Use automatic configuration script" from IE
|
||||
// settings. But, calling into WinHTTP can be slow especially when it has to call into
|
||||
// the out-of-process service to discover, load, and run the PAC file. So, we skip
|
||||
// calling into WinHTTP if there was a recent failure to detect a PAC file on the network.
|
||||
// This is a common error. The default IE settings on a Windows machine consist of the
|
||||
// single checkbox for "Automatically detect settings" turned on and most networks
|
||||
// won't actually discover a PAC file on the network since WPAD protocol isn't configured.
|
||||
if (_proxyHelper.AutoSettingsUsed && !_proxyHelper.RecentAutoDetectionFailure)
|
||||
{
|
||||
WINHTTP_PROXY_INFO proxyInfo = default;
|
||||
try
|
||||
{
|
||||
if (_proxyHelper.GetProxyForUrl(_sessionHandle, uri, out proxyInfo))
|
||||
{
|
||||
// If WinHTTP just specified a Proxy with no ProxyBypass list, then
|
||||
// we can return the Proxy uri directly.
|
||||
if (proxyInfo.ProxyBypass == IntPtr.Zero)
|
||||
{
|
||||
if (proxyInfo.Proxy != IntPtr.Zero)
|
||||
{
|
||||
string proxyStr = Marshal.PtrToStringUni(proxyInfo.Proxy)!;
|
||||
|
||||
return MultiProxy.CreateLazy(_failedProxies, proxyStr, IsSecureUri(uri));
|
||||
}
|
||||
else
|
||||
{
|
||||
return MultiProxy.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
// A bypass list was also specified. This means that WinHTTP has fallen back to
|
||||
// using the manual IE settings specified and there is a ProxyBypass list also.
|
||||
// Since we're not really using the full WinHTTP stack, we need to use HttpSystemProxy
|
||||
// to do the computation of the final proxy uri merging the information from the Proxy
|
||||
// and ProxyBypass strings.
|
||||
}
|
||||
else
|
||||
{
|
||||
return MultiProxy.Empty;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(proxyInfo.Proxy);
|
||||
Marshal.FreeHGlobal(proxyInfo.ProxyBypass);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to manual settings if present.
|
||||
if (_proxyHelper.ManualSettingsUsed)
|
||||
{
|
||||
if (_bypassLocal)
|
||||
{
|
||||
IPAddress? address;
|
||||
|
||||
if (uri.IsLoopback)
|
||||
{
|
||||
// This is optimization for loopback addresses.
|
||||
// Unfortunately this does not work for all local addresses.
|
||||
return MultiProxy.Empty;
|
||||
}
|
||||
|
||||
// Pre-Check if host may be IP address to avoid parsing.
|
||||
if (uri.HostNameType == UriHostNameType.IPv6 || uri.HostNameType == UriHostNameType.IPv4)
|
||||
{
|
||||
// RFC1123 allows labels to start with number.
|
||||
// Leading number may or may not be IP address.
|
||||
// IPv6 [::1] notation. '[' is not valid character in names.
|
||||
if (IPAddress.TryParse(uri.IdnHost, out address))
|
||||
{
|
||||
// Host is valid IP address.
|
||||
// Check if it belongs to local system.
|
||||
foreach (IPAddress a in _localIp!)
|
||||
{
|
||||
if (a.Equals(address))
|
||||
{
|
||||
return MultiProxy.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (uri.HostNameType != UriHostNameType.IPv6 && !uri.IdnHost.Contains('.'))
|
||||
{
|
||||
// Not address and does not have a dot.
|
||||
// Hosts without FQDN are considered local.
|
||||
return MultiProxy.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we have other rules for bypass.
|
||||
if (_bypass != null)
|
||||
{
|
||||
foreach (string entry in _bypass)
|
||||
{
|
||||
// IdnHost does not have [].
|
||||
if (SimpleRegex.IsMatchWithStarWildcard(uri.IdnHost, entry))
|
||||
{
|
||||
return MultiProxy.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We did not find match on bypass list.
|
||||
return IsSecureUri(uri) ? _secureProxy : _insecureProxy;
|
||||
}
|
||||
|
||||
return MultiProxy.Empty;
|
||||
}
|
||||
|
||||
private static bool IsSecureUri(Uri uri)
|
||||
{
|
||||
return uri.Scheme == UriScheme.Https || uri.Scheme == UriScheme.Wss;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if URI is subject to proxy or not.
|
||||
/// </summary>
|
||||
public bool IsBypassed(Uri uri)
|
||||
{
|
||||
// This HttpSystemProxy class is only consumed by SocketsHttpHandler and is not exposed outside of
|
||||
// SocketsHttpHandler. The current pattern for consumption of IWebProxy is to call IsBypassed first.
|
||||
// If it returns false, then the caller will call GetProxy. For this proxy implementation, computing
|
||||
// the return value for IsBypassed is as costly as calling GetProxy. We want to avoid doing extra
|
||||
// work. So, this proxy implementation for the IsBypassed method can always return false. Then the
|
||||
// GetProxy method will return non-null for a proxy, or null if no proxy should be used.
|
||||
return false;
|
||||
}
|
||||
|
||||
public ICredentials? Credentials
|
||||
{
|
||||
get
|
||||
{
|
||||
return _credentials;
|
||||
}
|
||||
set
|
||||
{
|
||||
_credentials = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Access function for unit tests.
|
||||
internal List<string>? BypassList => _bypass;
|
||||
}
|
||||
#pragma warning restore
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma warning disable
|
||||
using System.Net;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Http.DynamicProxy;
|
||||
|
||||
/// <summary>
|
||||
/// Copied from https://github.com/dotnet/runtime/blob/v8.0.0/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/IMultiWebProxy.cs
|
||||
/// </summary>
|
||||
internal interface IMultiWebProxy : IWebProxy
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the proxy URIs.
|
||||
/// </summary>
|
||||
public MultiProxy GetMultiProxy(Uri uri);
|
||||
}
|
||||
#pragma warning restore
|
||||
@@ -1,268 +0,0 @@
|
||||
#pragma warning disable
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Http.DynamicProxy;
|
||||
|
||||
/// <summary>
|
||||
/// Copied from https://github.com/dotnet/runtime/blob/v8.0.0/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/MultiProxy.cs
|
||||
/// </summary>
|
||||
internal struct MultiProxy
|
||||
{
|
||||
private readonly FailedProxyCache? _failedProxyCache;
|
||||
private readonly Uri[]? _uris;
|
||||
private readonly string? _proxyConfig;
|
||||
private readonly bool _secure;
|
||||
private int _currentIndex;
|
||||
private Uri? _currentUri;
|
||||
|
||||
private MultiProxy(FailedProxyCache? failedProxyCache, Uri[] uris)
|
||||
{
|
||||
_failedProxyCache = failedProxyCache;
|
||||
_uris = uris;
|
||||
_proxyConfig = null;
|
||||
_secure = default;
|
||||
_currentIndex = 0;
|
||||
_currentUri = null;
|
||||
}
|
||||
|
||||
private MultiProxy(FailedProxyCache failedProxyCache, string proxyConfig, bool secure)
|
||||
{
|
||||
_failedProxyCache = failedProxyCache;
|
||||
_uris = null;
|
||||
_proxyConfig = proxyConfig;
|
||||
_secure = secure;
|
||||
_currentIndex = 0;
|
||||
_currentUri = null;
|
||||
}
|
||||
|
||||
public static MultiProxy Empty => new MultiProxy(null, Array.Empty<Uri>());
|
||||
|
||||
/// <summary>
|
||||
/// Parses a WinHTTP proxy config into a MultiProxy instance.
|
||||
/// </summary>
|
||||
/// <param name="failedProxyCache">The cache of failed proxy requests to employ.</param>
|
||||
/// <param name="proxyConfig">The WinHTTP proxy config to parse.</param>
|
||||
/// <param name="secure">If true, return proxies suitable for use with a secure connection. If false, return proxies suitable for an insecure connection.</param>
|
||||
public static MultiProxy Parse(FailedProxyCache failedProxyCache, string? proxyConfig, bool secure)
|
||||
{
|
||||
Debug.Assert(failedProxyCache != null);
|
||||
|
||||
Uri[] uris = Array.Empty<Uri>();
|
||||
|
||||
ReadOnlySpan<char> span = proxyConfig;
|
||||
while (TryParseProxyConfigPart(span, secure, out Uri? uri, out int charactersConsumed))
|
||||
{
|
||||
int idx = uris.Length;
|
||||
|
||||
// Assume that we will typically not have more than 1...3 proxies, so just
|
||||
// grow by 1. This method is currently only used once per process, so the
|
||||
// case of an abnormally large config will not be much of a concern anyway.
|
||||
Array.Resize(ref uris, idx + 1);
|
||||
uris[idx] = uri;
|
||||
|
||||
span = span.Slice(charactersConsumed);
|
||||
}
|
||||
|
||||
return new MultiProxy(failedProxyCache, uris);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a MultiProxy instance that lazily parses a given WinHTTP configuration string.
|
||||
/// </summary>
|
||||
/// <param name="failedProxyCache">The cache of failed proxy requests to employ.</param>
|
||||
/// <param name="proxyConfig">The WinHTTP proxy config to parse.</param>
|
||||
/// <param name="secure">If true, return proxies suitable for use with a secure connection. If false, return proxies suitable for an insecure connection.</param>
|
||||
public static MultiProxy CreateLazy(FailedProxyCache failedProxyCache, string proxyConfig, bool secure)
|
||||
{
|
||||
Debug.Assert(failedProxyCache != null);
|
||||
|
||||
return string.IsNullOrEmpty(proxyConfig) == false ?
|
||||
new MultiProxy(failedProxyCache, proxyConfig, secure) :
|
||||
MultiProxy.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the next proxy URI from the MultiProxy.
|
||||
/// </summary>
|
||||
/// <param name="uri">The next proxy to use for the request.</param>
|
||||
/// <param name="isFinalProxy">If true, indicates there are no further proxies to read from the config.</param>
|
||||
/// <returns>If there is a proxy available, true. Otherwise, false.</returns>
|
||||
public bool ReadNext([NotNullWhen(true)] out Uri? uri, out bool isFinalProxy)
|
||||
{
|
||||
// Enumerating indicates the previous proxy has failed; mark it as such.
|
||||
if (_currentUri != null)
|
||||
{
|
||||
Debug.Assert(_failedProxyCache != null);
|
||||
_failedProxyCache.SetProxyFailed(_currentUri);
|
||||
}
|
||||
|
||||
// If no more proxies to read, return out quickly.
|
||||
if (!ReadNextHelper(out uri, out isFinalProxy))
|
||||
{
|
||||
_currentUri = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// If this is the first ReadNext() and all proxies are marked as failed, return the proxy that is closest to renewal.
|
||||
Uri? oldestFailedProxyUri = null;
|
||||
long oldestFailedProxyTicks = long.MaxValue;
|
||||
|
||||
do
|
||||
{
|
||||
Debug.Assert(_failedProxyCache != null);
|
||||
long renewTicks = _failedProxyCache.GetProxyRenewTicks(uri);
|
||||
|
||||
// Proxy hasn't failed recently, return for use.
|
||||
if (renewTicks == FailedProxyCache.Immediate)
|
||||
{
|
||||
_currentUri = uri;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (renewTicks < oldestFailedProxyTicks)
|
||||
{
|
||||
oldestFailedProxyUri = uri;
|
||||
oldestFailedProxyTicks = renewTicks;
|
||||
}
|
||||
}
|
||||
while (ReadNextHelper(out uri, out isFinalProxy));
|
||||
|
||||
// All the proxies in the config have failed; in this case, return the proxy that is closest to renewal.
|
||||
if (_currentUri == null)
|
||||
{
|
||||
uri = oldestFailedProxyUri;
|
||||
_currentUri = oldestFailedProxyUri;
|
||||
|
||||
if (oldestFailedProxyUri != null)
|
||||
{
|
||||
Debug.Assert(uri != null);
|
||||
_failedProxyCache.TryRenewProxy(uri, oldestFailedProxyTicks);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the next proxy URI from the MultiProxy, either via parsing a config string or from an array.
|
||||
/// </summary>
|
||||
private bool ReadNextHelper([NotNullWhen(true)] out Uri? uri, out bool isFinalProxy)
|
||||
{
|
||||
Debug.Assert(_uris != null || _proxyConfig != null, $"{nameof(ReadNext)} must not be called on a default-initialized {nameof(MultiProxy)}.");
|
||||
|
||||
if (_uris != null)
|
||||
{
|
||||
if (_currentIndex == _uris.Length)
|
||||
{
|
||||
uri = default;
|
||||
isFinalProxy = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
uri = _uris[_currentIndex++];
|
||||
isFinalProxy = _currentIndex == _uris.Length;
|
||||
return true;
|
||||
}
|
||||
|
||||
Debug.Assert(_proxyConfig != null);
|
||||
if (_currentIndex < _proxyConfig.Length)
|
||||
{
|
||||
bool hasProxy = TryParseProxyConfigPart(_proxyConfig.AsSpan(_currentIndex), _secure, out uri!, out int charactersConsumed);
|
||||
|
||||
_currentIndex += charactersConsumed;
|
||||
Debug.Assert(_currentIndex <= _proxyConfig.Length);
|
||||
|
||||
isFinalProxy = _currentIndex == _proxyConfig.Length;
|
||||
|
||||
return hasProxy;
|
||||
}
|
||||
|
||||
uri = default;
|
||||
isFinalProxy = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is used to parse WinINet Proxy strings, a single proxy at a time.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The strings are a semicolon or whitespace separated list, with each entry in the following format:
|
||||
/// ([<scheme>=][<scheme>"://"]<server>[":"<port>])
|
||||
/// </remarks>
|
||||
private static bool TryParseProxyConfigPart(ReadOnlySpan<char> proxyString, bool secure, [NotNullWhen(true)] out Uri? uri, out int charactersConsumed)
|
||||
{
|
||||
const int SECURE_FLAG = 1;
|
||||
const int INSECURE_FLAG = 2;
|
||||
const string ProxyDelimiters = "; \n\r\t";
|
||||
|
||||
int wantedFlag = secure ? SECURE_FLAG : INSECURE_FLAG;
|
||||
int originalLength = proxyString.Length;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Skip any delimiters.
|
||||
int iter = 0;
|
||||
while (iter < proxyString.Length && ProxyDelimiters.Contains(proxyString[iter]))
|
||||
{
|
||||
++iter;
|
||||
}
|
||||
|
||||
if (iter == proxyString.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
proxyString = proxyString.Slice(iter);
|
||||
|
||||
// Determine which scheme this part is for.
|
||||
// If no schema is defined, use both.
|
||||
int proxyType = SECURE_FLAG | INSECURE_FLAG;
|
||||
|
||||
if (proxyString.StartsWith("http="))
|
||||
{
|
||||
proxyType = INSECURE_FLAG;
|
||||
proxyString = proxyString.Slice("http=".Length);
|
||||
}
|
||||
else if (proxyString.StartsWith("https="))
|
||||
{
|
||||
proxyType = SECURE_FLAG;
|
||||
proxyString = proxyString.Slice("https=".Length);
|
||||
}
|
||||
|
||||
if (proxyString.StartsWith("http://"))
|
||||
{
|
||||
proxyType = INSECURE_FLAG;
|
||||
proxyString = proxyString.Slice("http://".Length);
|
||||
}
|
||||
else if (proxyString.StartsWith("https://"))
|
||||
{
|
||||
proxyType = SECURE_FLAG;
|
||||
proxyString = proxyString.Slice("https://".Length);
|
||||
}
|
||||
|
||||
// Find the next delimiter, or end of string.
|
||||
iter = proxyString.IndexOfAny(ProxyDelimiters);
|
||||
if (iter < 0)
|
||||
{
|
||||
iter = proxyString.Length;
|
||||
}
|
||||
|
||||
// Return URI if it's a match to what we want.
|
||||
if ((proxyType & wantedFlag) != 0 && Uri.TryCreate(string.Concat("http://", proxyString.Slice(0, iter)), UriKind.Absolute, out uri))
|
||||
{
|
||||
charactersConsumed = originalLength - proxyString.Length + iter;
|
||||
Debug.Assert(charactersConsumed > 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
proxyString = proxyString.Slice(iter);
|
||||
}
|
||||
|
||||
uri = null;
|
||||
charactersConsumed = originalLength;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#pragma warning restore
|
||||
@@ -1,73 +0,0 @@
|
||||
#pragma warning disable
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Http.DynamicProxy;
|
||||
|
||||
/// <summary>
|
||||
/// Copied from https://github.com/dotnet/runtime/blob/v8.0.0/src/libraries/Common/src/System/Text/SimpleRegex.cs
|
||||
/// </summary>
|
||||
internal static class SimpleRegex
|
||||
{
|
||||
// Based on wildcmp written by Jack Handy - <A href="mailto:jakkhandy@hotmail.com">jakkhandy@hotmail.com</A>
|
||||
// https://www.codeproject.com/Articles/1088/Wildcard-string-compare-globbing
|
||||
|
||||
/// <summary>
|
||||
/// Perform a match between an input string and a pattern in which the only special character
|
||||
/// is an asterisk, which can map to zero or more of any character in the input.
|
||||
/// </summary>
|
||||
/// <param name="input">The input to match.</param>
|
||||
/// <param name="pattern">The pattern to match against.</param>
|
||||
/// <returns>true if the input matches the pattern; otherwise, false.</returns>
|
||||
public static bool IsMatchWithStarWildcard(ReadOnlySpan<char> input, ReadOnlySpan<char> pattern)
|
||||
{
|
||||
int inputPos = 0, inputPosSaved = -1;
|
||||
int patternPos = 0, patternPosSaved = -1;
|
||||
|
||||
// Loop through each character in the input.
|
||||
while (inputPos < input.Length)
|
||||
{
|
||||
if (patternPos < pattern.Length && pattern[patternPos] == '*')
|
||||
{
|
||||
// If we're currently positioned on a wildcard in the pattern,
|
||||
// move past it and remember where we are to backtrack to.
|
||||
inputPosSaved = inputPos;
|
||||
patternPosSaved = ++patternPos;
|
||||
}
|
||||
else if (patternPos < pattern.Length &&
|
||||
(pattern[patternPos] == input[inputPos] ||
|
||||
char.ToUpperInvariant(pattern[patternPos]) == char.ToUpperInvariant(input[inputPos])))
|
||||
{
|
||||
// If the characters in the pattern and the input match, advance both.
|
||||
inputPos++;
|
||||
patternPos++;
|
||||
}
|
||||
else if (patternPosSaved == -1)
|
||||
{
|
||||
// If we're not on a wildcard and the current characters don't match and we don't have
|
||||
// any wildcard to backtrack to, this is not a match.
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, this is not a wildcard, the characters don't match, but we do have a
|
||||
// wildcard saved, so backtrack to it and use it to consume the next input character.
|
||||
inputPos = ++inputPosSaved;
|
||||
patternPos = patternPosSaved;
|
||||
}
|
||||
}
|
||||
|
||||
// We've reached the end of the input. Eat all wildcards immediately after where we are
|
||||
// in the pattern, as if they're at the end, they'll all just map to nothing (and if it
|
||||
// turns out there's something after them, eating them won't matter).
|
||||
while (patternPos < pattern.Length && pattern[patternPos] == '*')
|
||||
{
|
||||
patternPos++;
|
||||
}
|
||||
|
||||
// If we are in fact at the end of the pattern, then we successfully matched.
|
||||
// If there's anything left, it's not a wildcard, so it doesn't match.
|
||||
Debug.Assert(patternPos <= pattern.Length);
|
||||
return patternPos == pattern.Length;
|
||||
}
|
||||
}
|
||||
#pragma warning restore
|
||||
@@ -1,24 +0,0 @@
|
||||
#pragma warning disable
|
||||
namespace Snap.Hutao.Core.IO.Http.DynamicProxy;
|
||||
|
||||
/// <summary>
|
||||
/// Copied from https://github.com/dotnet/runtime/blob/v8.0.0/src/libraries/Common/src/System/Net/UriScheme.cs
|
||||
/// </summary>
|
||||
internal static class UriScheme
|
||||
{
|
||||
public const string File = "file";
|
||||
public const string Ftp = "ftp";
|
||||
public const string Gopher = "gopher";
|
||||
public const string Http = "http";
|
||||
public const string Https = "https";
|
||||
public const string News = "news";
|
||||
public const string NetPipe = "net.pipe";
|
||||
public const string NetTcp = "net.tcp";
|
||||
public const string Nntp = "nntp";
|
||||
public const string Mailto = "mailto";
|
||||
public const string Ws = "ws";
|
||||
public const string Wss = "wss";
|
||||
|
||||
public const string SchemeDelimiter = "://";
|
||||
}
|
||||
#pragma warning restore
|
||||
@@ -1,179 +0,0 @@
|
||||
#pragma warning disable
|
||||
using System.Runtime.InteropServices;
|
||||
using static Snap.Hutao.Win32.Interop.WinHttp;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Http.DynamicProxy;
|
||||
|
||||
/// <summary>
|
||||
/// Copied from https://github.com/dotnet/runtime/blob/v8.0.0/src/libraries/Common/src/System/Net/Http/WinInetProxyHelper.cs
|
||||
/// </summary>
|
||||
internal sealed class WinInetProxyHelper
|
||||
{
|
||||
private const int RecentAutoDetectionInterval = 120_000; // 2 minutes in milliseconds.
|
||||
private readonly string? _autoConfigUrl, _proxy, _proxyBypass;
|
||||
private readonly bool _autoDetect;
|
||||
private readonly bool _useProxy;
|
||||
private bool _autoDetectionFailed;
|
||||
private int _lastTimeAutoDetectionFailed; // Environment.TickCount units (milliseconds).
|
||||
|
||||
public WinInetProxyHelper()
|
||||
{
|
||||
WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig = default;
|
||||
|
||||
try
|
||||
{
|
||||
if (WinHttpGetIEProxyConfigForCurrentUser(out proxyConfig))
|
||||
{
|
||||
_autoConfigUrl = Marshal.PtrToStringUni(proxyConfig.AutoConfigUrl)!;
|
||||
_autoDetect = proxyConfig.AutoDetect != 0;
|
||||
_proxy = Marshal.PtrToStringUni(proxyConfig.Proxy)!;
|
||||
_proxyBypass = Marshal.PtrToStringUni(proxyConfig.ProxyBypass)!;
|
||||
|
||||
_useProxy = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We match behavior of WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY and ignore errors.
|
||||
int lastError = Marshal.GetLastWin32Error();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// FreeHGlobal already checks for null pointer before freeing the memory.
|
||||
Marshal.FreeHGlobal(proxyConfig.AutoConfigUrl);
|
||||
Marshal.FreeHGlobal(proxyConfig.Proxy);
|
||||
Marshal.FreeHGlobal(proxyConfig.ProxyBypass);
|
||||
}
|
||||
}
|
||||
|
||||
public string? AutoConfigUrl => _autoConfigUrl;
|
||||
|
||||
public bool AutoDetect => _autoDetect;
|
||||
|
||||
public bool AutoSettingsUsed => AutoDetect || !string.IsNullOrEmpty(AutoConfigUrl);
|
||||
|
||||
public bool ManualSettingsUsed => !string.IsNullOrEmpty(Proxy);
|
||||
|
||||
public bool ManualSettingsOnly => !AutoSettingsUsed && ManualSettingsUsed;
|
||||
|
||||
public string? Proxy => _proxy;
|
||||
|
||||
public string? ProxyBypass => _proxyBypass;
|
||||
|
||||
public bool RecentAutoDetectionFailure =>
|
||||
_autoDetectionFailed &&
|
||||
Environment.TickCount - _lastTimeAutoDetectionFailed <= RecentAutoDetectionInterval;
|
||||
|
||||
public bool GetProxyForUrl(
|
||||
SafeWinHttpHandle? sessionHandle,
|
||||
Uri uri,
|
||||
out WINHTTP_PROXY_INFO proxyInfo)
|
||||
{
|
||||
proxyInfo.AccessType = WINHTTP_ACCESS_TYPE_NO_PROXY;
|
||||
proxyInfo.Proxy = IntPtr.Zero;
|
||||
proxyInfo.ProxyBypass = IntPtr.Zero;
|
||||
|
||||
if (!_useProxy)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool useProxy = false;
|
||||
|
||||
WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions;
|
||||
autoProxyOptions.AutoConfigUrl = AutoConfigUrl;
|
||||
autoProxyOptions.AutoDetectFlags = AutoDetect ?
|
||||
(WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A) : 0;
|
||||
autoProxyOptions.AutoLoginIfChallenged = false;
|
||||
autoProxyOptions.Flags =
|
||||
(AutoDetect ? WINHTTP_AUTOPROXY_AUTO_DETECT : 0) |
|
||||
(!string.IsNullOrEmpty(AutoConfigUrl) ? WINHTTP_AUTOPROXY_CONFIG_URL : 0);
|
||||
autoProxyOptions.Reserved1 = IntPtr.Zero;
|
||||
autoProxyOptions.Reserved2 = 0;
|
||||
|
||||
// AutoProxy Cache.
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/WinHttp/autoproxy-cache
|
||||
// If the out-of-process service is active when WinHttpGetProxyForUrl is called, the cached autoproxy
|
||||
// URL and script are available to the whole computer. However, if the out-of-process service is used,
|
||||
// and the fAutoLogonIfChallenged flag in the pAutoProxyOptions structure is true, then the autoproxy
|
||||
// URL and script are not cached. Therefore, calling WinHttpGetProxyForUrl with the fAutoLogonIfChallenged
|
||||
// member set to TRUE results in additional overhead operations that may affect performance.
|
||||
// The following steps can be used to improve performance:
|
||||
// 1. Call WinHttpGetProxyForUrl with the fAutoLogonIfChallenged parameter set to false. The autoproxy
|
||||
// URL and script are cached for future calls to WinHttpGetProxyForUrl.
|
||||
// 2. If Step 1 fails, with ERROR_WINHTTP_LOGIN_FAILURE, then call WinHttpGetProxyForUrl with the
|
||||
// fAutoLogonIfChallenged member set to TRUE.
|
||||
//
|
||||
// We match behavior of WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY and ignore errors.
|
||||
#pragma warning disable CA1845 // file is shared with a build that lacks string.Concat for spans
|
||||
// Underlying code does not understand WebSockets so we need to convert it to http or https.
|
||||
string destination = uri.AbsoluteUri;
|
||||
if (uri.Scheme == UriScheme.Wss)
|
||||
{
|
||||
destination = UriScheme.Https + destination.Substring(UriScheme.Wss.Length);
|
||||
}
|
||||
else if (uri.Scheme == UriScheme.Ws)
|
||||
{
|
||||
destination = UriScheme.Http + destination.Substring(UriScheme.Ws.Length);
|
||||
}
|
||||
#pragma warning restore CA1845
|
||||
|
||||
var repeat = false;
|
||||
do
|
||||
{
|
||||
_autoDetectionFailed = false;
|
||||
if (WinHttpGetProxyForUrl(
|
||||
sessionHandle!,
|
||||
destination,
|
||||
ref autoProxyOptions,
|
||||
out proxyInfo))
|
||||
{
|
||||
useProxy = true;
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
var lastError = Marshal.GetLastWin32Error();
|
||||
|
||||
if (lastError == ERROR_WINHTTP_LOGIN_FAILURE)
|
||||
{
|
||||
if (repeat)
|
||||
{
|
||||
// We don't retry more than once.
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
repeat = true;
|
||||
autoProxyOptions.AutoLoginIfChallenged = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lastError == ERROR_WINHTTP_AUTODETECTION_FAILED)
|
||||
{
|
||||
_autoDetectionFailed = true;
|
||||
_lastTimeAutoDetectionFailed = Environment.TickCount;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (repeat);
|
||||
|
||||
// Fall back to manual settings if available.
|
||||
if (!useProxy && !string.IsNullOrEmpty(Proxy))
|
||||
{
|
||||
proxyInfo.AccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
|
||||
proxyInfo.Proxy = Marshal.StringToHGlobalUni(Proxy);
|
||||
proxyInfo.ProxyBypass = string.IsNullOrEmpty(ProxyBypass) ?
|
||||
IntPtr.Zero : Marshal.StringToHGlobalUni(ProxyBypass);
|
||||
|
||||
useProxy = true;
|
||||
}
|
||||
|
||||
return useProxy;
|
||||
}
|
||||
}
|
||||
#pragma warning restore
|
||||
@@ -7,7 +7,7 @@ using Windows.Win32.System.Registry;
|
||||
|
||||
namespace Snap.Hutao.Core.Shell;
|
||||
|
||||
internal sealed partial class RegistryMonitor : IDisposable
|
||||
internal sealed partial class RegistryWatcher : IDisposable
|
||||
{
|
||||
private readonly ManualResetEvent eventTerminate = new(false);
|
||||
private readonly CancellationTokenSource cancellationTokenSource = new();
|
||||
@@ -20,7 +20,7 @@ internal sealed partial class RegistryMonitor : IDisposable
|
||||
private string subKey = default!;
|
||||
private bool disposed;
|
||||
|
||||
public RegistryMonitor(string name, EventHandler eventHandler)
|
||||
public RegistryWatcher(string name, EventHandler eventHandler)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(name);
|
||||
|
||||
@@ -30,9 +30,9 @@ internal sealed partial class RegistryMonitor : IDisposable
|
||||
|
||||
public event EventHandler? RegChanged;
|
||||
|
||||
public static RegistryMonitor Create(string name, EventHandler eventHandler)
|
||||
public static RegistryWatcher Create(string name, EventHandler eventHandler)
|
||||
{
|
||||
return new RegistryMonitor(name, eventHandler);
|
||||
return new RegistryWatcher(name, eventHandler);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
Reference in New Issue
Block a user