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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
namespace XFESystem.Browser;

public readonly record struct BrowserResourceReceipt(string Address, string Kind, int Characters, string? RequestedAddress = null);

/// <summary>One navigation's bounded resource fetches, with the same TLS/mixed-content rules as its document.</summary>
internal sealed class BrowserResourceLoader(IWebTransport transport, IBrowserResourceSource resources, Uri documentAddress, BrowserLimits limits, bool bundledOnly = false)
{
    private int count;
    private long characters;
    private readonly Dictionary<string, WebResponse> prefetched = new(StringComparer.Ordinal);
    public List<BrowserResourceReceipt> Receipts { get; } = [];
    public bool LastResponsePackaged { get; private set; }

    public WebResponse Get(Uri target, string kind, IReadOnlyDictionary<string, string>? headers = null)
    {
        BrowserService.ValidateWebAddress(target);
        if (kind == "script" && prefetched.Remove(target.AbsoluteUri, out var cached))
        {
            LastResponsePackaged = false;
            Receipts.Add(new(cached.Address.AbsoluteUri, kind, cached.Body.Length, target.AbsoluteUri));
            return cached;
        }
        if (bundledOnly && target.GetLeftPart(UriPartial.Authority) != PackagedBrowserResources.Origin)
            throw new InvalidOperationException("Local document subresources must be bundled with XFESystem.");
        if (++count > limits.MaximumExternalResources) throw new BrowserLimitException("The external resource count limit was reached.");
        if (documentAddress.Scheme == "https" && target.Scheme != "https")
            throw new InvalidOperationException("An HTTPS document cannot load an HTTP subresource.");
        var requestLimits = limits with { MaximumHtmlCharacters = Math.Min(8_388_608, limits.MaximumExternalResourceCharacters) };
        WebResponse response;
        LastResponsePackaged = resources.TryGet(target, out response!);
        if (!LastResponsePackaged)
        {
            if (bundledOnly || target.GetLeftPart(UriPartial.Authority) == PackagedBrowserResources.Origin)
                throw new FileNotFoundException("The packaged browser resource does not exist: " + target.AbsolutePath);
            if (kind == "fetch" && transport is IWebRequestTransport requestTransport) response = requestTransport.Get(target, requestLimits, headers ?? new Dictionary<string, string>());
            else if (headers is { Count: > 0 }) throw new NotSupportedException("The transport cannot send script request headers.");
            else response = transport.Get(target, requestLimits);
            if (BrowserService.IsReservedAddress(response.Address))
                throw new InvalidOperationException("A network subresource cannot impersonate a bundled resource.");
        }
        if (bundledOnly && response.Address.GetLeftPart(UriPartial.Authority) != PackagedBrowserResources.Origin)
            throw new InvalidOperationException("A local document resource cannot redirect outside its bundled origin.");
        BrowserService.ValidateWebAddress(response.Address);
        if ((target.Scheme == "https" || documentAddress.Scheme == "https") && response.Address.Scheme != "https")
            throw new InvalidOperationException("An HTTPS subresource cannot downgrade to HTTP.");
        if (response.Address.Scheme == "https" && !response.CertificateValidated)
            throw new InvalidOperationException("Subresource HTTPS certificate validation was not confirmed.");
        if (response.StatusCode is < 200 or >= 300) throw new IOException("Subresource HTTP status " + response.StatusCode + ".");
        int maximum = kind == "style" ? limits.MaximumStyleCharacters : limits.MaximumExternalResourceCharacters;
        if (response.Body.Length > maximum || (characters += response.Body.Length) > limits.MaximumTotalResourceCharacters)
            throw new BrowserLimitException("The external resources exceed the navigation's character budget.");
        if (response.BinaryBody is { } bytes && (bytes.Length > limits.MaximumImageBytes || (characters += bytes.Length) > limits.MaximumTotalResourceCharacters))
            throw new BrowserLimitException("The image exceeds the navigation's resource budget.");
        var mime = response.ContentType.Split(';')[0].Trim().ToLowerInvariant();
        bool javaScript = mime is "text/javascript" or "application/javascript" or "application/ecmascript" or "text/ecmascript" or "application/x-javascript";
        if (kind == "script" && (response.NoSniff && !javaScript || mime.StartsWith("image/", StringComparison.Ordinal) ||
            mime.StartsWith("audio/", StringComparison.Ordinal) || mime.StartsWith("video/", StringComparison.Ordinal) || mime == "text/csv"))
            throw new NotSupportedException("The script response has an unsupported MIME type: " + mime + " (" + target.GetLeftPart(UriPartial.Path) + ")");
        if (kind == "style" && mime != "text/css") throw new NotSupportedException("The stylesheet response is not text/css.");
        if (kind == "image" && !mime.StartsWith("image/", StringComparison.Ordinal)) throw new NotSupportedException("The image response is not an image (" + target.GetLeftPart(UriPartial.Path) + ").");
        if (kind == "fetch" && mime is "text/javascript" or "application/javascript" or "application/ecmascript" or "text/ecmascript" or "application/x-javascript")
            prefetched[target.AbsoluteUri] = response;
        Receipts.Add(new(response.Address.AbsoluteUri, kind, response.Body.Length, target.AbsoluteUri));
        return response;
    }
}