using AngleSharp.Xml.Parser; using AngleSharp.Dom; using AngleSharp.Html.Parser; using Jint.Native; using Jint.Runtime.Interop; namespace XFESystem.Browser; public sealed partial class DomBridge { private readonly Dictionary parsedDocuments = new(ReferenceEqualityComparer.Instance); private readonly HashSet inertParsedScripts = new(ReferenceEqualityComparer.Instance); private long parsedCharacters; internal bool IsInertParsedScript(IElement script) => inertParsedScripts.Contains(script); private JsValue CloneDomNode(INode original, bool deep) { var clone = original.Clone(deep); var result = Created(clone); var sources = SelfAndDescendants(original).Where(element => element.LocalName == "script"); var copies = SelfAndDescendants(clone).Where(element => element.LocalName == "script"); foreach (var pair in sources.Zip(copies)) if (inertParsedScripts.Contains(pair.First)) inertParsedScripts.Add(pair.Second); return result; } private void InstallDomParserCallback() => engine.SetValue("__xfeParseDom", new ClrFunction(engine, "parseFromString", (_, args) => { string source = LimitedText(Value(args, 0)), mime = Argument(args, 1); if (mime is not ("text/html" or "text/xml" or "application/xml" or "application/xhtml+xml" or "image/svg+xml")) throw AdjacentError("TypeError", "Unsupported DOMParser MIME type.", 0); if (parsedDocuments.Count >= 32 || parsedCharacters + source.Length > limits.MaximumHtmlCharacters) throw new BrowserLimitException("DOMParser exceeds the page's detached-document budget."); IDocument parsed; if (mime == "text/html") parsed = new HtmlParser().ParseDocument(source); else { try { RejectXmlDeclarations(source); int nodes = 0; parsed = new XmlParser(new XmlParserOptions { IsSuppressingErrors = false, OnCreated = (_, _) => { if (++nodes > limits.MaximumDomNodes) throw new BrowserLimitException("XML parsing exceeds its node budget."); } }).ParseDocument(source); } catch (Exception error) when (error is XmlParseException or FormatException) { parsed = new HtmlParser().ParseDocument(""); foreach (var child in parsed.ChildNodes.ToArray()) parsed.RemoveChild(child); var element = parsed.CreateElement("http://www.mozilla.org/newlayout/xml/parsererror.xml", "parsererror"); element.TextContent = "XML parsing failed."; parsed.AppendChild(element); } } ValidateDocument(parsed, limits); if (parsedDocuments.Sum(pair => pair.Key.All.Count()) + parsed.All.Count() > limits.MaximumDomNodes) throw new BrowserLimitException("DOMParser exceeds the page's detached node budget."); parsedCharacters += source.Length; parsedDocuments.Add(parsed, mime); foreach (var script in parsed.QuerySelectorAll("script")) inertParsedScripts.Add(script); return Wrap(parsed); })); private static void RejectXmlDeclarations(string source) { // DOMParser never loads external entities or accepts document type // declarations. Skip inert comment/CDATA text before rejecting markup. for (int at = 0; (at = source.IndexOf("= 0;) { string terminator; if (source.AsSpan(at).StartsWith(""; else if (source.AsSpan(at).StartsWith(""; else throw new FormatException("XML declarations are unsupported."); int end = source.IndexOf(terminator, at + 2, StringComparison.Ordinal); if (end < 0) throw new FormatException("Unclosed XML comment/CDATA."); at = end + terminator.Length; } } }