use reflect to reduce code size

This commit is contained in:
qhy040404
2024-01-08 20:19:05 +08:00
parent 92c1b12764
commit 8710150897
12 changed files with 19 additions and 1729 deletions

View File

@@ -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";
}
}

View File

@@ -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
}
}
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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:
/// ([&lt;scheme&gt;=][&lt;scheme&gt;"://"]&lt;server&gt;[":"&lt;port&gt;])
/// </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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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()