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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
using AngleSharp.Dom;

namespace XFESystem.Browser.Tests;

/// <summary>Explicit transport-boundary tests; no HTTP server or host browser is involved.</summary>
public sealed class BrowserTrustBoundaryRegressionTests
{
    public static IEnumerable<object[]> ReservedFinalAddresses()
    {
        foreach (var address in new[]
        {
            "http://xfesystem.local/Browser/Monaco/Index.html",
            "http://XFESYSTEM.LOCAL:80/Browser/Monaco/Index.html?language=csharp",
            "http://XfeSystem.Local:8080/Browser/Monaco/Index.html",
            "https://xfesystem.local/Browser/Monaco/Index.html",
            "https://XFESYSTEM.LOCAL:443/Browser/Monaco/Index.html?language=csharp",
            "https://XfeSystem.Local:444/Browser/Monaco/Index.html",
            "http://xfesystem.local:65535/Documents/Preview/Index.html"
        }) yield return [address];
    }

    public static IEnumerable<object[]> ReservedSubresourceAddresses()
    {
        foreach (var row in ReservedFinalAddresses())
        {
            yield return [row[0], "script"];
            yield return [row[0], "style"];
        }
    }

    [Theory]
    [MemberData(nameof(ReservedFinalAddresses))]
    public void NetworkFinalAddressCannotImpersonateAnyReservedHostOrAcquireThePackagedScriptBudget(string finalAddress)
    {
        const string requested = "http://network.test/Redirect";
        var html = "<title>Forged packaged page</title><script>globalThis.RedirectCanary='EXECUTED';</script><script>" +
            new string(' ', 140 * 1024) + "document.title='Oversized script executed';</script>";
        var transport = new BoundaryTransport((_, _) => new(new(finalAddress), 200, "text/html", html, CertificateValidated: true));
        var trace = new List<string>();
        var browser = new BrowserService(transport) { Trace = trace.Add };

        var page = browser.Navigate(requested);

        Assert.Equal("Navigation failed", page.Title);
        Assert.Contains("A network response cannot impersonate a packaged XFESystem page.", page.Text, StringComparison.Ordinal);
        Assert.DoesNotContain("Oversized script executed", page.Text, StringComparison.Ordinal);
        Assert.Empty(trace); // No document script/style dispatch began.
        AssertCleared(browser);
        Assert.Equal(requested, Assert.Single(transport.Requests).AbsoluteUri);
        Assert.Equal("undefined", browser.ExecuteJavaScript("typeof RedirectCanary"));
    }

    [Theory]
    [MemberData(nameof(ReservedSubresourceAddresses))]
    public void NetworkSubresourceRedirectsToReservedHostsAreRejectedBeforeExecutionOrStyleAdoption(string finalAddress, string kind)
    {
        const string requested = "http://network.test/Page";
        var subresource = kind == "script" ? "Foreign.js" : "Foreign.css";
        var include = kind == "script" ? $"<script src='{subresource}'></script>" : $"<link rel='stylesheet' href='{subresource}'>";
        var transport = new BoundaryTransport((address, _) => address.AbsolutePath == "/Page"
            ? new(address, 200, "text/html", "<title>Ordinary network page</title><div id='Canary' style='background:#192A3B;color:#556677'>Untouched</div>" +
                include + "<script>globalThis.OrdinaryTail=42;</script>", true)
            : new(new(finalAddress), 200, kind == "script" ? "application/javascript" : "text/css",
                kind == "script" ? "globalThis.SubresourceCanary='EXECUTED';document.getElementById('Canary').textContent='PWNED';"
                    : "#Canary{background:#FF3355;color:#112233;width:313px}", true));
        var browser = new BrowserService(transport);

        var page = browser.Navigate(requested);

        Assert.Equal("Ordinary network page", page.Title);
        Assert.Contains(kind == "script" ? "External script failed:" : "Stylesheet failed:", page.Text, StringComparison.Ordinal);
        Assert.Contains("A network subresource cannot impersonate a bundled resource.", page.Text, StringComparison.Ordinal);
        var document = Assert.IsAssignableFrom<IDocument>(browser.Document);
        Assert.Equal("Untouched", document.GetElementById("Canary")!.TextContent);
        Assert.Equal("undefined", browser.ExecuteJavaScript("typeof SubresourceCanary"));
        Assert.Equal("42", browser.ExecuteJavaScript("OrdinaryTail"));
        var style = browser.Layout(new(600, 400)).GetComputedStyle(document.GetElementById("Canary")!);
        Assert.NotNull(style);
        Assert.Equal(0x192A3Bu, style.Background.Rgb);
        Assert.Equal(0x556677u, style.Color.Rgb);
        Assert.Empty(browser.LoadedResources);
        Assert.Equal("{}", browser.ReadWebsiteDocumentProof());
        Assert.Equal("{}", browser.ReadMonacoProof());
        Assert.Equal(new[] { requested, "http://network.test/" + subresource }, transport.Requests.Select(uri => uri.AbsoluteUri));
    }

    [Fact]
    public void ExpandedLocalDocumentFailureClearsPreviousRemoteDomResourcesAndTrustThenAllowsNormalNavigation()
    {
        var transport = new BoundaryTransport((address, _) => address.AbsolutePath switch
        {
            "/Before" => new(address, 200, "text/html", "<title>Before</title><div class='markdown-body' id='OldRemote'>Old remote sentinel</div>" +
                "<link rel='stylesheet' href='/Previous.css'><script>globalThis.PreviousPageCanary=72;</script>", true),
            "/Previous.css" => new(address, 200, "text/css", "#OldRemote{color:#123456}", true),
            "/After" => new(address, 200, "text/html", "<title>After</title><p id='Recovered'>Fresh ordinary page</p><script>globalThis.PageRecovered=42;</script>", true),
            _ => throw new InvalidOperationException("Unexpected boundary fixture address: " + address)
        });
        var browser = new BrowserService(transport);
        browser.Navigate("http://network.test/Before");
        Assert.NotNull(browser.Document!.QuerySelector(".markdown-body#OldRemote"));
        Assert.Single(browser.LoadedResources);
        Assert.Equal("72", browser.ExecuteJavaScript("PreviousPageCanary"));

        // Source fits the one-MiB source limit, while HTML entity expansion alone
        // produces over one MiB. The failure therefore occurs inside LoadHtml.
        var error = Assert.Throws<IOException>(() => browser.NavigateDocument(new string('&', 256 * 1024), "Expanded.md"));

        Assert.Contains("Document preview failed:", error.Message, StringComparison.Ordinal);
        Assert.Contains("The document exceeds its character limit.", error.Message, StringComparison.Ordinal);
        Assert.IsType<BrowserLimitException>(error.InnerException);
        Assert.Equal("Navigation failed", browser.CurrentPage.Title);
        Assert.DoesNotContain("Old remote sentinel", browser.CurrentPage.Text, StringComparison.Ordinal);
        AssertCleared(browser);

        var after = browser.Navigate("http://network.test/After");
        Assert.Equal("After", after.Title);
        Assert.Equal("Fresh ordinary page", browser.Document!.GetElementById("Recovered")!.TextContent);
        Assert.Null(browser.Document.GetElementById("OldRemote"));
        Assert.Equal("undefined", browser.ExecuteJavaScript("typeof PreviousPageCanary"));
        Assert.Equal("42", browser.ExecuteJavaScript("PageRecovered"));
        var oversized = browser.ExecuteJavaScript(new string(' ', new BrowserLimits().MaximumScriptCharacters + 1) + "globalThis.AccidentalTrustedBudget=true;");
        Assert.Contains("The script exceeds its character limit.", oversized, StringComparison.Ordinal);
        Assert.Equal("undefined", browser.ExecuteJavaScript("typeof AccidentalTrustedBudget"));
        Assert.Empty(browser.LoadedResources);
        Assert.Equal("{}", browser.ReadWebsiteDocumentProof());
        Assert.Equal("{}", browser.ReadMonacoProof());
    }

    private static void AssertCleared(BrowserService browser)
    {
        Assert.Null(browser.Document);
        Assert.Empty(browser.LoadedResources);
        Assert.Equal("{}", browser.ReadWebsiteDocumentProof());
        Assert.Equal("{}", browser.ReadMonacoProof());
        Assert.Null(browser.Document); // Read-only proof calls cannot recreate a stale DOM.
    }

    private sealed class BoundaryTransport(Func<Uri, BrowserLimits, WebResponse> respond) : IWebTransport
    {
        public List<Uri> Requests { get; } = [];
        public WebResponse Get(Uri address, BrowserLimits limits)
        { Requests.Add(address); return respond(address, limits); }
    }
}