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;

/// <summary>A conservative nonce/strict-dynamic CSP profile. Unknown policies still fail closed.
/// eval, inline event attributes, modules and workers remain unavailable.</summary>
internal sealed class BrowserCspScriptPolicy
{
    private readonly List<HashSet<string>> nonces = [];
    private readonly HashSet<IElement> parserScripts;
    private readonly bool declared, understood = true;
    public BrowserCspScriptPolicy(string? header, IDocument document)
    {
        parserScripts = new(document.QuerySelectorAll("script"), ReferenceEqualityComparer.Instance);
        var policies = new List<string>();
        if (header is not null) policies.AddRange(header.Split(','));
        foreach (var meta in document.QuerySelectorAll("meta[http-equiv]"))
            if (meta.GetAttribute("http-equiv")!.Equals("Content-Security-Policy", StringComparison.OrdinalIgnoreCase))
                policies.Add(meta.GetAttribute("content") ?? "");
        declared = policies.Count != 0;
        foreach (string policy in policies)
        {
            if (policy.Length is 0 or > 65_536 || policy.Any(c => c != '\t' && (c < ' ' || c > '~')))
            { understood = false; continue; }
            var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            HashSet<string>? sources = null;
            foreach (string directive in policy.Split(';'))
            {
                var tokens = directive.Split([' ', '\t'], StringSplitOptions.RemoveEmptyEntries);
                if (tokens.Length == 0 || !seen.Add(tokens[0])) continue; // CSP uses the first occurrence.
                switch (tokens[0].ToLowerInvariant())
                {
                    case "script-src":
                        sources = new(StringComparer.Ordinal);
                        bool strict = tokens.Contains("'strict-dynamic'");
                        foreach (string token in tokens.Skip(1))
                        {
                            if (token.StartsWith("'nonce-", StringComparison.Ordinal) && token.EndsWith('\''))
                            {
                                string nonce = token[7..^1];
                                string unpadded = nonce.TrimEnd('=');
                                if (unpadded.Length != 0 && nonce.Length - unpadded.Length <= 2 && unpadded.All(c => char.IsAsciiLetterOrDigit(c) || c is '+' or '/' or '-' or '_')) sources.Add(nonce);
                                else understood = false;
                            }
                            else if (token is not ("https:" or "'strict-dynamic'" or "'report-sample'" or "'wasm-unsafe-eval'")) understood = false;
                        }
                        if (!strict || sources.Count == 0) understood = false;
                        break;
                    case "base-uri":
                        if (tokens.Length != 2 || tokens[1] != "'self'") understood = false;
                        break;
                    case "report-to":
                        if (tokens.Length != 2 || !tokens[1].All(c => char.IsAsciiLetterOrDigit(c) || c is '-' or '_' or '.')) understood = false;
                        break;
                    default: understood = false; break;
                }
            }
            if (sources is null) understood = false;
            else nonces.Add(sources);
        }
    }

    internal bool BlocksAllScripts => declared && !understood;
    internal bool Allows(IElement script, bool createdByApi)
    {
        if (!declared) return true;
        if (!understood) return false;
        // Only scripts created through the executing realm's DOM API enter the
        // dynamic queue. innerHTML/fragment scripts remain inert in DomBridge.
        bool dynamic = !parserScripts.Contains(script) && createdByApi;
        bool nonceable = !script.Attributes.Any(attribute => attribute.Name.Contains("<script", StringComparison.OrdinalIgnoreCase) ||
            attribute.Name.Contains("<style", StringComparison.OrdinalIgnoreCase) || attribute.Value.Contains("<script", StringComparison.OrdinalIgnoreCase) ||
            attribute.Value.Contains("<style", StringComparison.OrdinalIgnoreCase));
        return nonces.All(policy => nonceable && policy.Contains(script.GetAttribute("nonce") ?? "") || dynamic);
    }

    internal bool AllowsBase(Uri document, Uri candidate) => !declared ||
        understood && document.Scheme == candidate.Scheme && document.IdnHost == candidate.IdnHost && document.Port == candidate.Port;
}