XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Globalization;
using System.Net;
using System.Text;
using Org.BouncyCastle.Crypto.Digests;
using XFESystem.Browser;
using XFESystem.NativeRuntime.Application.Network;
using XFESystem.NativeRuntime.Application.Security;
using XFESystem.Tls;
using WebResponse = XFESystem.Browser.WebResponse;

namespace XFESystem.NativeRuntime.Application.Web;

/// <summary>Guest e1000/TCP plus managed TLS. No host socket, proxy, cache or certificate bypass.</summary>
internal sealed class NativeWebTransport : IWebTransport, IWebTransportCapabilities
{
    private static readonly object gate = new();
    private static TlsCertificateValidator? certificates;
    private static int requestId;
    public bool IsNative => true;
    public bool SupportsHttp => true;
    public bool SupportsHttps => true;

    internal NativeWebTransport() => NativeNetwork.ConfigureEntropy(NativeTlsSecurity.Random32);

    private static TlsCertificateValidator Roots()
    {
        if (certificates is not null) return certificates;
        long started = NativePlatform.Milliseconds();
        NativePlatform.Write("XFE_NATIVE_TLS_TRUST_IMPORT_BEGIN\n");
        using var stream = typeof(NativeWebTransport).Assembly.GetManifestResourceStream("Security/MozillaRoots.pem")
            ?? throw new IOException("The explicit trusted root snapshot is missing.");
        using var reader = new StreamReader(stream, Encoding.ASCII);
        certificates = TlsCertificateValidator.FromPem(reader.ReadToEnd());
        NativePlatform.Write("XFE_NATIVE_TLS_PREREQUISITES|Clock|CMOS_UTC|EntropyCapabilities|" + NativeTlsSecurity.Capabilities +
            "|RootSnapshot|Mozilla20260925|HostCertificateStore|0|ImportMilliseconds|" + (NativePlatform.Milliseconds() - started) + "\n");
        return certificates;
    }

    public WebResponse Get(Uri address, BrowserLimits limits)
    {
        try { return GetCore(address, limits); }
        catch (Exception error) when (error is not OutOfMemoryException and not StackOverflowException)
        {
            var details = new StringBuilder();
            for (Exception? current = error; current is not null && details.Length < 8192; current = current.InnerException)
                details.Append(current.GetType().FullName).Append(": ").Append(current.Message).Append('\n').Append(current.StackTrace).Append('\n');
            NativePlatform.Write("XFE_NATIVE_WEB_ERROR_BEGIN\n" + details + "XFE_NATIVE_WEB_ERROR_END\n");
            throw;
        }
    }

    private static WebResponse GetCore(Uri address, BrowserLimits limits)
    {
        ArgumentNullException.ThrowIfNull(address); ArgumentNullException.ThrowIfNull(limits);
        if (limits.NetworkTimeout <= TimeSpan.Zero || limits.NetworkTimeout > TimeSpan.FromMinutes(2))
            throw new ArgumentOutOfRangeException(nameof(limits));
        long deadline = checked(NativePlatform.Milliseconds() + (long)limits.NetworkTimeout.TotalMilliseconds);
        if (!Monitor.TryEnter(gate, Remaining(deadline))) throw new TimeoutException("The native web transport ownership deadline expired.");
        try
        {
            Uri target = address;
            for (int redirects = 0; ; redirects++)
            {
                ValidateAddress(target);
                int id = ++requestId;
                string host = target.IdnHost;
                string authority = target.IsDefaultPort ? host : host + ":" + target.Port.ToString(CultureInfo.InvariantCulture);
                string headers = "GET " + target.PathAndQuery + " HTTP/1.1\r\nHost: " + authority +
                    "\r\nUser-Agent: Mozilla/5.0 (XFESystem; x86_64) XFEWeb/0.3\r\n" +
                    "Accept: text/html,application/xhtml+xml,text/css,application/javascript,text/plain,*/*;q=0.5\r\n" +
                    "Accept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\nAccept-Encoding: identity\r\nConnection: close\r\n\r\n";
                NativePlatform.Write("XFE_NATIVE_WEB_REQUEST|" + id + "|" + target.AbsoluteUri + "\n");
                using Stream tcp = NativeNetwork.OpenTcp(host, target.Port, Remaining(deadline));
                using var bounded = new DeadlineStream(tcp, deadline);
                long handshakeStarted = NativePlatform.Milliseconds();
                using TlsClientConnection? tls = target.Scheme == "https"
                    ? new TlsClientConnection(bounded, host, Roots(), NativeTlsSecurity.Context) : null;
                if (tls is not null) NativePlatform.Write("XFE_NATIVE_TLS_CONNECTED|Host|" + host + "|Verified|" +
                    (tls.CertificateValidated ? 1 : 0) + "|Protocol|" + tls.Protocol + "|InitializationAndHandshakeMilliseconds|" +
                    (NativePlatform.Milliseconds() - handshakeStarted) + "\n");
                Stream wire = tls?.Stream ?? bounded;
                byte[] request = Encoding.ASCII.GetBytes(headers);
                wire.Write(request, 0, request.Length); wire.Flush();
                var response = NativeHttpProtocol.ReadResponse((buffer, offset, count) =>
                    wire.Read(buffer, offset, count), checked(limits.MaximumHtmlCharacters * 4));
                string body = NativeHttpProtocol.DecodeBody(response);
                if (body.Length > limits.MaximumHtmlCharacters) throw new BrowserLimitException("The decoded response exceeds its character limit.");
                bool verified = tls?.CertificateValidated == true;
                EmitResponse(id, target, headers, response, body, verified, tls?.Protocol ?? "HTTP");
                if (response.StatusCode is 301 or 302 or 303 or 307 or 308)
                {
                    if (redirects >= limits.MaximumRedirects) throw new IOException("The HTTP redirect limit was reached.");
                    if (string.IsNullOrWhiteSpace(response.Location) || !Uri.TryCreate(target, response.Location, out var next))
                        throw new IOException("The redirect Location is invalid.");
                    ValidateAddress(next);
                    if (target.Scheme == "https" && next.Scheme != "https") throw new IOException("HTTPS redirects cannot downgrade to HTTP.");
                    target = next; continue;
                }
                return new(target, response.StatusCode, response.ContentType, body, verified, response.ContentSecurityPolicy, Hash(body));
            }
        }
        finally { Monitor.Exit(gate); }
    }

    private static int Remaining(long deadline)
    {
        long remaining = deadline - NativePlatform.Milliseconds();
        if (remaining <= 0) throw new TimeoutException("The native web request deadline expired.");
        return (int)Math.Min(remaining, int.MaxValue);
    }
    private static void ValidateAddress(Uri address)
    {
        if (!address.IsAbsoluteUri || address.Scheme is not ("http" or "https") ||
            string.IsNullOrEmpty(address.Host) || !string.IsNullOrEmpty(address.UserInfo))
            throw new NotSupportedException("Only HTTP and HTTPS URLs without embedded credentials are supported.");
    }
    private static string Hash(string body) => Hash(Encoding.UTF8.GetBytes(body));
    private static string Hash(byte[] bytes)
    {
        byte[] digest = new byte[32];
        var sha = new Sha256Digest(); sha.BlockUpdate(bytes, 0, bytes.Length); sha.DoFinal(digest, 0);
        return Convert.ToHexString(digest).ToLowerInvariant();
    }
    internal static string Quote(string? value)
    {
        if (value is null) return "null";
        var builder = new StringBuilder("\"");
        foreach (char c in value)
            if (c is '\\' or '"') builder.Append('\\').Append(c);
            else if (c < 32) builder.Append("\\u").Append(((int)c).ToString("x4", CultureInfo.InvariantCulture));
            else builder.Append(c);
        return builder.Append('"').ToString();
    }
    private static void EmitResponse(int id, Uri address, string requestHeaders, NativeHttpResponse response, string body, bool verified, string protocol)
    {
        string title = ""; int start = body.IndexOf("<title", StringComparison.OrdinalIgnoreCase);
        if (start >= 0 && (start = body.IndexOf('>', start)) >= 0)
        { int end = body.IndexOf("</title", start, StringComparison.OrdinalIgnoreCase); if (end > start) title = WebUtility.HtmlDecode(body.Substring(start + 1, Math.Min(512, end - start - 1))); }
        string responseHeaders = response.StatusLine + "\r\n" + string.Join("\r\n", response.Headers.Select(pair => pair.Key + ": " + pair.Value));
        string json = "{\"Kind\":\"ActualGuestWebResponse\",\"RequestId\":" + id + ",\"RequestUri\":" + Quote(address.AbsoluteUri) +
            ",\"ResponseUri\":" + Quote(address.AbsoluteUri) + ",\"StatusCode\":" + response.StatusCode + ",\"ContentType\":" + Quote(response.ContentType) +
            ",\"CertificateValidated\":" + (verified ? "true" : "false") + ",\"NativeTransport\":true,\"Protocol\":" + Quote(protocol) +
            ",\"RequestHeaders\":" + Quote(requestHeaders) + ",\"ResponseHeaders\":" + Quote(responseHeaders) +
            ",\"BodyBytes\":" + response.Body.Length + ",\"BodySha256\":" + Quote(Hash(body)) +
            ",\"RawBodySha256\":" + Quote(Hash(response.Body)) +
            ",\"BodyHashMeaning\":\"UTF8 of decoded HTML/text\",\"HtmlTitle\":" + Quote(title) +
            ",\"HasHtml\":" + (body.Contains("<html", StringComparison.OrdinalIgnoreCase) ? "true" : "false") +
            ",\"HasHead\":" + (body.Contains("<head", StringComparison.OrdinalIgnoreCase) ? "true" : "false") +
            ",\"Location\":" + Quote(response.Location) + "}";
        NativePlatform.Write("XFE_NATIVE_WEB_RESPONSE_BEGIN\n" + json + "\nXFE_NATIVE_WEB_RESPONSE_END\n");
    }

    private sealed class DeadlineStream(Stream inner, long deadline) : Stream
    {
        public override bool CanRead => true; public override bool CanWrite => true; public override bool CanSeek => false;
        public override long Length => throw new NotSupportedException();
        public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
        private void Check()
        {
            int left = Remaining(deadline);
            if (!inner.CanTimeout) throw new IOException("The native TCP stream cannot enforce bounded web I/O.");
            inner.ReadTimeout = left; inner.WriteTimeout = left;
        }
        public override int Read(byte[] buffer, int offset, int count) { Check(); int read = inner.Read(buffer, offset, count); Check(); return read; }
        public override void Write(byte[] buffer, int offset, int count) { Check(); inner.Write(buffer, offset, count); Check(); }
        public override void Flush() { Check(); inner.Flush(); }
        public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
        public override void SetLength(long value) => throw new NotSupportedException();
    }
}