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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using Xunit;

namespace XFESystem.Documents.Tests;

public sealed class DocumentPreviewTests
{
    [Fact]
    public void MarkdownPreviewUsesRealBundledWebsiteAssetsAndRestrictsNetwork()
    {
        var preview = new DocumentPreviewBuilder(new()).Markdown("# Document\n<script>alert(1)</script>", "<Title>");
        Assert.Contains("&lt;Title&gt;", preview.Html);
        Assert.DoesNotContain("alert(1)", preview.Html);
        Assert.Contains("data-manual-doc-init=\"true\"", preview.Html);
        Assert.Contains("connect-src 'none'", preview.ContentSecurityPolicy);
        Assert.Contains("base-uri 'none'", preview.ContentSecurityPolicy);
        foreach (var name in WebsiteAssets.StyleNames.Concat(WebsiteAssets.ScriptNames))
        {
            Assert.Contains(WebsiteAssets.BaseUri + name, preview.Html);
            Assert.NotEmpty(WebsiteAssets.ReadText(name));
        }
    }

    [Theory]
    [InlineData("Test.js", "javascript")]
    [InlineData("Test.CS", "csharp")]
    [InlineData("Test.xaml", "xaml")]
    [InlineData("Test.md", "markdown")]
    [InlineData("Test.unknown", "plaintext")]
    public void CodePreviewsEscapeHtmlAndUseWebsiteLanguageMapping(string path, string language)
    {
        var preview = new DocumentPreviewBuilder(new()).Code("</code><script>danger()</script>", path);
        Assert.Contains("language-" + language, preview.Html);
        Assert.Contains("&lt;/code&gt;&lt;script&gt;danger()&lt;/script&gt;", preview.Html);
        Assert.DoesNotContain("<script>danger()", preview.Html);
    }

    [Fact]
    public void JavaScriptPreviewHasExplicitIsolationRequirementsAndDoesNotInterpolateExecutableHtml()
    {
        const string source = "console.log('中文'); const text = '</script><img src=x onerror=bad()>';";
        var plan = DocumentPreviewBuilder.JavaScript(source);
        Assert.True(plan.RequiresSeparateProcess);
        Assert.False(plan.AllowNetwork);
        Assert.False(plan.AllowHostObjects);
        Assert.Equal("allow-scripts", plan.IframeSandbox);
        Assert.DoesNotContain("allow-same-origin", plan.IframeSandbox);
        Assert.DoesNotContain("<img", plan.Html);
        Assert.Contains("connect-src 'none'", plan.Html);
        var encoded = Regex.Match(plan.Html, "data:text/javascript;base64,([^\"]+)").Groups[1].Value;
        Assert.Equal(source, Encoding.UTF8.GetString(Convert.FromBase64String(encoded)));
    }

    [Fact]
    public void JavaScriptPreviewRejectsUnboundedBudgets()
    {
        Assert.Throws<ArgumentOutOfRangeException>(() => DocumentPreviewBuilder.JavaScript("", TimeSpan.Zero));
        Assert.Throws<ArgumentOutOfRangeException>(() => DocumentPreviewBuilder.JavaScript("", TimeSpan.FromMinutes(1)));
        Assert.Throws<ArgumentOutOfRangeException>(() => DocumentPreviewBuilder.JavaScript("", memoryLimitBytes: long.MaxValue));
        Assert.Throws<ArgumentException>(() => DocumentPreviewBuilder.JavaScript(new string('x', 1024 * 1024 + 1)));
    }

    [Fact]
    public void BundledAssetsMatchTheWebsiteProvenanceManifest()
    {
        using var manifest = JsonDocument.Parse(WebsiteAssets.ReadText("SourceManifest.json"));
        Assert.Equal("95b43e16776aa648bca19d0060d01d3d2a52196e", manifest.RootElement.GetProperty("SourceCommit").GetString());
        foreach (var entry in manifest.RootElement.GetProperty("Entries").EnumerateArray())
        {
            var path = entry.GetProperty("SnapshotPath").GetString()!;
            if (path.StartsWith("../", StringComparison.Ordinal)) continue;
            using var asset = WebsiteAssets.Open(path);
            Assert.Equal(entry.GetProperty("SnapshotSha256").GetString(), Convert.ToHexStringLower(SHA256.HashData(asset)));
        }
    }

    [Fact]
    public void ResourcePathsCannotEscapeTheEmbeddedBundle()
    {
        Assert.Throws<ArgumentException>(() => WebsiteAssets.Open("../Secret"));
        Assert.Throws<ArgumentException>(() => WebsiteAssets.Open("Fonts\\Secret"));
        Assert.Throws<FileNotFoundException>(() => WebsiteAssets.Open("Missing.txt"));
    }

    [Fact]
    public void MathFontStylesResolveEveryBundledFontWithoutNetworkFallback()
    {
        foreach (Match match in Regex.Matches(WebsiteAssets.ReadText("Katex.min.css"), @"url\(([^)]+)\)"))
        {
            var fontName = match.Groups[1].Value;
            Assert.StartsWith("Fonts/", fontName);
            using var stream = WebsiteAssets.Open(fontName);
            Assert.True(stream.Length > 0);
        }
    }
}