using System.Net; using System.Security.Cryptography; using System.Text; namespace XFESystem.Documents; public sealed partial class DocumentPreviewBuilder(MarkdownRenderer markdownRenderer) { public PreviewDocument Markdown(string text, string title = "Markdown", string language = "zh-CN") => Build(markdownRenderer.RenderHtml(text), title, language); public PreviewDocument Code(string text, string path, string language = "zh-CN") => Build($"
{E(text)}
", path, language); private static PreviewDocument Build(string body, string title, string language) { var nonce = Convert.ToBase64String(RandomNumberGenerator.GetBytes(24)); const string resourceOrigin = "https://xfe-resources.invalid"; var policy = $"default-src 'none'; script-src 'nonce-{nonce}' {resourceOrigin}; style-src {resourceOrigin} 'unsafe-inline'; " + $"font-src {resourceOrigin}; img-src data: blob:; connect-src 'none'; object-src 'none'; frame-src 'none'; base-uri 'none'; form-action 'none'"; var styles = string.Join("\n", WebsiteAssets.StyleNames.Select(name => $"")); var scripts = string.Join("\n", WebsiteAssets.ScriptNames.Select(name => $"")); // The website's auto initializer is disabled because its image modal depends on Bootstrap. // Keep its actual code tools, math and Mermaid functions, using local resources only. var html = $$$""" {{{E(title)}}} {{{styles}}}
{{{body}}}
{{{scripts}}} """; return new PreviewDocument(html, true, policy); } public static JavaScriptPreviewPlan JavaScript(string source, TimeSpan? timeout = null, long memoryLimitBytes = 64 * 1024 * 1024) { ArgumentNullException.ThrowIfNull(source); var actualTimeout = timeout ?? TimeSpan.FromSeconds(2); if (actualTimeout <= TimeSpan.Zero || actualTimeout > TimeSpan.FromSeconds(30)) throw new ArgumentOutOfRangeException(nameof(timeout)); if (memoryLimitBytes < 4 * 1024 * 1024 || memoryLimitBytes > 512 * 1024 * 1024) throw new ArgumentOutOfRangeException(nameof(memoryLimitBytes)); if (Encoding.UTF8.GetByteCount(source) > 1024 * 1024) throw new ArgumentException("JavaScript source exceeds the preview limit.", nameof(source)); var nonce = Convert.ToBase64String(RandomNumberGenerator.GetBytes(24)); var code = Convert.ToBase64String(Encoding.UTF8.GetBytes(source)); // Base64 data URL prevents in user code from escaping into HTML. No eval or host bridge. var html = $$"""

            
            """;
        return new JavaScriptPreviewPlan(html, actualTimeout, memoryLimitBytes);
    }

    private static string E(string text) => WebUtility.HtmlEncode(text);
}