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<IDocument, string> parsedDocuments = new(ReferenceEqualityComparer.Instance);
private readonly HashSet<IElement> 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("<!", at, StringComparison.Ordinal)) >= 0;)
{
string terminator;
if (source.AsSpan(at).StartsWith("<!--", StringComparison.Ordinal)) terminator = "-->";
else if (source.AsSpan(at).StartsWith("<![CDATA[", StringComparison.Ordinal)) terminator = "]]>";
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;
}
}
}
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<IDocument, string> parsedDocuments = new(ReferenceEqualityComparer.Instance);
private readonly HashSet<IElement> 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("<!", at, StringComparison.Ordinal)) >= 0;)
{
string terminator;
if (source.AsSpan(at).StartsWith("<!--", StringComparison.Ordinal)) terminator = "-->";
else if (source.AsSpan(at).StartsWith("<![CDATA[", StringComparison.Ordinal)) terminator = "]]>";
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;
}
}
}