diff options
Diffstat (limited to '')
-rw-r--r-- | dom/base/nsTreeSanitizer.cpp | 2643 |
1 files changed, 2643 insertions, 0 deletions
diff --git a/dom/base/nsTreeSanitizer.cpp b/dom/base/nsTreeSanitizer.cpp new file mode 100644 index 0000000000..d2fb443deb --- /dev/null +++ b/dom/base/nsTreeSanitizer.cpp @@ -0,0 +1,2643 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsTreeSanitizer.h" + +#include "mozilla/Algorithm.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/DeclarationBlock.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StyleSheetInlines.h" +#include "mozilla/dom/DocumentFragment.h" +#include "mozilla/dom/HTMLFormElement.h" +#include "mozilla/dom/HTMLTemplateElement.h" +#include "mozilla/dom/HTMLUnknownElement.h" +#include "mozilla/dom/Link.h" +#include "mozilla/dom/SanitizerBinding.h" +#include "mozilla/dom/ShadowIncludingTreeIterator.h" +#include "mozilla/dom/SRIMetadata.h" +#include "mozilla/NullPrincipal.h" +#include "nsAtom.h" +#include "nsCSSPropertyID.h" +#include "nsHashtablesFwd.h" +#include "nsString.h" +#include "nsTHashtable.h" +#include "nsUnicharInputStream.h" +#include "nsAttrName.h" +#include "nsIScriptError.h" +#include "nsIScriptSecurityManager.h" +#include "nsNameSpaceManager.h" +#include "nsNetUtil.h" +#include "nsComponentManagerUtils.h" +#include "nsContentUtils.h" +#include "nsIParserUtils.h" +#include "mozilla/dom/Document.h" +#include "nsQueryObject.h" + +#include <iterator> + +using namespace mozilla; +using namespace mozilla::dom; + +// +// Thanks to Mark Pilgrim and Sam Ruby for the initial whitelist +// +const nsStaticAtom* const kElementsHTML[] = { + // clang-format off + nsGkAtoms::a, + nsGkAtoms::abbr, + nsGkAtoms::acronym, + nsGkAtoms::address, + nsGkAtoms::area, + nsGkAtoms::article, + nsGkAtoms::aside, + nsGkAtoms::audio, + nsGkAtoms::b, + nsGkAtoms::bdi, + nsGkAtoms::bdo, + nsGkAtoms::big, + nsGkAtoms::blockquote, + // body checked specially + nsGkAtoms::br, + nsGkAtoms::button, + nsGkAtoms::canvas, + nsGkAtoms::caption, + nsGkAtoms::center, + nsGkAtoms::cite, + nsGkAtoms::code, + nsGkAtoms::col, + nsGkAtoms::colgroup, + nsGkAtoms::data, + nsGkAtoms::datalist, + nsGkAtoms::dd, + nsGkAtoms::del, + nsGkAtoms::details, + nsGkAtoms::dfn, + nsGkAtoms::dialog, + nsGkAtoms::dir, + nsGkAtoms::div, + nsGkAtoms::dl, + nsGkAtoms::dt, + nsGkAtoms::em, + nsGkAtoms::fieldset, + nsGkAtoms::figcaption, + nsGkAtoms::figure, + nsGkAtoms::font, + nsGkAtoms::footer, + nsGkAtoms::form, + nsGkAtoms::h1, + nsGkAtoms::h2, + nsGkAtoms::h3, + nsGkAtoms::h4, + nsGkAtoms::h5, + nsGkAtoms::h6, + // head checked specially + nsGkAtoms::header, + nsGkAtoms::hgroup, + nsGkAtoms::hr, + // html checked specially + nsGkAtoms::i, + nsGkAtoms::img, + nsGkAtoms::input, + nsGkAtoms::ins, + nsGkAtoms::kbd, + nsGkAtoms::keygen, + nsGkAtoms::label, + nsGkAtoms::legend, + nsGkAtoms::li, + nsGkAtoms::link, + nsGkAtoms::listing, + nsGkAtoms::main, + nsGkAtoms::map, + nsGkAtoms::mark, + nsGkAtoms::menu, + nsGkAtoms::meta, + nsGkAtoms::meter, + nsGkAtoms::nav, + nsGkAtoms::nobr, + nsGkAtoms::noscript, + nsGkAtoms::ol, + nsGkAtoms::optgroup, + nsGkAtoms::option, + nsGkAtoms::output, + nsGkAtoms::p, + nsGkAtoms::picture, + nsGkAtoms::pre, + nsGkAtoms::progress, + nsGkAtoms::q, + nsGkAtoms::rb, + nsGkAtoms::rp, + nsGkAtoms::rt, + nsGkAtoms::rtc, + nsGkAtoms::ruby, + nsGkAtoms::s, + nsGkAtoms::samp, + nsGkAtoms::section, + nsGkAtoms::select, + nsGkAtoms::small, + nsGkAtoms::source, + nsGkAtoms::span, + nsGkAtoms::strike, + nsGkAtoms::strong, + nsGkAtoms::sub, + nsGkAtoms::summary, + nsGkAtoms::sup, + // style checked specially + nsGkAtoms::table, + nsGkAtoms::tbody, + nsGkAtoms::td, + // template checked and traversed specially + nsGkAtoms::textarea, + nsGkAtoms::tfoot, + nsGkAtoms::th, + nsGkAtoms::thead, + nsGkAtoms::time, + // title checked specially + nsGkAtoms::tr, + nsGkAtoms::track, + nsGkAtoms::tt, + nsGkAtoms::u, + nsGkAtoms::ul, + nsGkAtoms::var, + nsGkAtoms::video, + nsGkAtoms::wbr, + nullptr + // clang-format on +}; + +const nsStaticAtom* const kAttributesHTML[] = { + // clang-format off + nsGkAtoms::abbr, + nsGkAtoms::accept, + nsGkAtoms::acceptcharset, + nsGkAtoms::accesskey, + nsGkAtoms::action, + nsGkAtoms::alt, + nsGkAtoms::as, + nsGkAtoms::autocomplete, + nsGkAtoms::autofocus, + nsGkAtoms::autoplay, + nsGkAtoms::axis, + nsGkAtoms::_char, + nsGkAtoms::charoff, + nsGkAtoms::charset, + nsGkAtoms::checked, + nsGkAtoms::cite, + nsGkAtoms::_class, + nsGkAtoms::cols, + nsGkAtoms::colspan, + nsGkAtoms::content, + nsGkAtoms::contenteditable, + nsGkAtoms::contextmenu, + nsGkAtoms::controls, + nsGkAtoms::coords, + nsGkAtoms::crossorigin, + nsGkAtoms::datetime, + nsGkAtoms::dir, + nsGkAtoms::disabled, + nsGkAtoms::draggable, + nsGkAtoms::enctype, + nsGkAtoms::face, + nsGkAtoms::_for, + nsGkAtoms::frame, + nsGkAtoms::headers, + nsGkAtoms::height, + nsGkAtoms::hidden, + nsGkAtoms::high, + nsGkAtoms::href, + nsGkAtoms::hreflang, + nsGkAtoms::icon, + nsGkAtoms::id, + nsGkAtoms::integrity, + nsGkAtoms::ismap, + nsGkAtoms::itemid, + nsGkAtoms::itemprop, + nsGkAtoms::itemref, + nsGkAtoms::itemscope, + nsGkAtoms::itemtype, + nsGkAtoms::kind, + nsGkAtoms::label, + nsGkAtoms::lang, + nsGkAtoms::list_, + nsGkAtoms::longdesc, + nsGkAtoms::loop, + nsGkAtoms::low, + nsGkAtoms::max, + nsGkAtoms::maxlength, + nsGkAtoms::media, + nsGkAtoms::method, + nsGkAtoms::min, + nsGkAtoms::minlength, + nsGkAtoms::multiple, + nsGkAtoms::muted, + nsGkAtoms::name, + nsGkAtoms::nohref, + nsGkAtoms::novalidate, + nsGkAtoms::nowrap, + nsGkAtoms::open, + nsGkAtoms::optimum, + nsGkAtoms::pattern, + nsGkAtoms::placeholder, + nsGkAtoms::playbackrate, + nsGkAtoms::poster, + nsGkAtoms::preload, + nsGkAtoms::prompt, + nsGkAtoms::pubdate, + nsGkAtoms::radiogroup, + nsGkAtoms::readonly, + nsGkAtoms::rel, + nsGkAtoms::required, + nsGkAtoms::rev, + nsGkAtoms::reversed, + nsGkAtoms::role, + nsGkAtoms::rows, + nsGkAtoms::rowspan, + nsGkAtoms::rules, + nsGkAtoms::scoped, + nsGkAtoms::scope, + nsGkAtoms::selected, + nsGkAtoms::shape, + nsGkAtoms::span, + nsGkAtoms::spellcheck, + nsGkAtoms::src, + nsGkAtoms::srclang, + nsGkAtoms::start, + nsGkAtoms::summary, + nsGkAtoms::tabindex, + nsGkAtoms::target, + nsGkAtoms::title, + nsGkAtoms::type, + nsGkAtoms::usemap, + nsGkAtoms::value, + nsGkAtoms::width, + nsGkAtoms::wrap, + nullptr + // clang-format on +}; + +const nsStaticAtom* const kPresAttributesHTML[] = { + // clang-format off + nsGkAtoms::align, + nsGkAtoms::background, + nsGkAtoms::bgcolor, + nsGkAtoms::border, + nsGkAtoms::cellpadding, + nsGkAtoms::cellspacing, + nsGkAtoms::color, + nsGkAtoms::compact, + nsGkAtoms::clear, + nsGkAtoms::hspace, + nsGkAtoms::noshade, + nsGkAtoms::pointSize, + nsGkAtoms::size, + nsGkAtoms::valign, + nsGkAtoms::vspace, + nullptr + // clang-format on +}; + +// List of HTML attributes with URLs that the +// browser will fetch. Should be kept in sync with +// https://html.spec.whatwg.org/multipage/indices.html#attributes-3 +const nsStaticAtom* const kURLAttributesHTML[] = { + // clang-format off + nsGkAtoms::action, + nsGkAtoms::href, + nsGkAtoms::src, + nsGkAtoms::longdesc, + nsGkAtoms::cite, + nsGkAtoms::background, + nsGkAtoms::formaction, + nsGkAtoms::data, + nsGkAtoms::ping, + nsGkAtoms::poster, + nullptr + // clang-format on +}; + +const nsStaticAtom* const kElementsSVG[] = { + nsGkAtoms::a, // a + nsGkAtoms::circle, // circle + nsGkAtoms::clipPath, // clipPath + nsGkAtoms::colorProfile, // color-profile + nsGkAtoms::cursor, // cursor + nsGkAtoms::defs, // defs + nsGkAtoms::desc, // desc + nsGkAtoms::ellipse, // ellipse + nsGkAtoms::elevation, // elevation + nsGkAtoms::erode, // erode + nsGkAtoms::ex, // ex + nsGkAtoms::exact, // exact + nsGkAtoms::exponent, // exponent + nsGkAtoms::feBlend, // feBlend + nsGkAtoms::feColorMatrix, // feColorMatrix + nsGkAtoms::feComponentTransfer, // feComponentTransfer + nsGkAtoms::feComposite, // feComposite + nsGkAtoms::feConvolveMatrix, // feConvolveMatrix + nsGkAtoms::feDiffuseLighting, // feDiffuseLighting + nsGkAtoms::feDisplacementMap, // feDisplacementMap + nsGkAtoms::feDistantLight, // feDistantLight + nsGkAtoms::feDropShadow, // feDropShadow + nsGkAtoms::feFlood, // feFlood + nsGkAtoms::feFuncA, // feFuncA + nsGkAtoms::feFuncB, // feFuncB + nsGkAtoms::feFuncG, // feFuncG + nsGkAtoms::feFuncR, // feFuncR + nsGkAtoms::feGaussianBlur, // feGaussianBlur + nsGkAtoms::feImage, // feImage + nsGkAtoms::feMerge, // feMerge + nsGkAtoms::feMergeNode, // feMergeNode + nsGkAtoms::feMorphology, // feMorphology + nsGkAtoms::feOffset, // feOffset + nsGkAtoms::fePointLight, // fePointLight + nsGkAtoms::feSpecularLighting, // feSpecularLighting + nsGkAtoms::feSpotLight, // feSpotLight + nsGkAtoms::feTile, // feTile + nsGkAtoms::feTurbulence, // feTurbulence + nsGkAtoms::filter, // filter + nsGkAtoms::font, // font + nsGkAtoms::font_face, // font-face + nsGkAtoms::font_face_format, // font-face-format + nsGkAtoms::font_face_name, // font-face-name + nsGkAtoms::font_face_src, // font-face-src + nsGkAtoms::font_face_uri, // font-face-uri + nsGkAtoms::foreignObject, // foreignObject + nsGkAtoms::g, // g + // glyph + nsGkAtoms::glyphRef, // glyphRef + // hkern + nsGkAtoms::image, // image + nsGkAtoms::line, // line + nsGkAtoms::linearGradient, // linearGradient + nsGkAtoms::marker, // marker + nsGkAtoms::mask, // mask + nsGkAtoms::metadata, // metadata + nsGkAtoms::missingGlyph, // missingGlyph + nsGkAtoms::mpath, // mpath + nsGkAtoms::path, // path + nsGkAtoms::pattern, // pattern + nsGkAtoms::polygon, // polygon + nsGkAtoms::polyline, // polyline + nsGkAtoms::radialGradient, // radialGradient + nsGkAtoms::rect, // rect + nsGkAtoms::stop, // stop + nsGkAtoms::svg, // svg + nsGkAtoms::svgSwitch, // switch + nsGkAtoms::symbol, // symbol + nsGkAtoms::text, // text + nsGkAtoms::textPath, // textPath + nsGkAtoms::title, // title + nsGkAtoms::tref, // tref + nsGkAtoms::tspan, // tspan + nsGkAtoms::use, // use + nsGkAtoms::view, // view + // vkern + nullptr}; + +constexpr const nsStaticAtom* const kAttributesSVG[] = { + // accent-height + nsGkAtoms::accumulate, // accumulate + nsGkAtoms::additive, // additive + nsGkAtoms::alignment_baseline, // alignment-baseline + // alphabetic + nsGkAtoms::amplitude, // amplitude + // arabic-form + // ascent + nsGkAtoms::attributeName, // attributeName + nsGkAtoms::attributeType, // attributeType + nsGkAtoms::azimuth, // azimuth + nsGkAtoms::baseFrequency, // baseFrequency + nsGkAtoms::baseline_shift, // baseline-shift + // baseProfile + // bbox + nsGkAtoms::begin, // begin + nsGkAtoms::bias, // bias + nsGkAtoms::by, // by + nsGkAtoms::calcMode, // calcMode + // cap-height + nsGkAtoms::_class, // class + nsGkAtoms::clip_path, // clip-path + nsGkAtoms::clip_rule, // clip-rule + nsGkAtoms::clipPathUnits, // clipPathUnits + nsGkAtoms::color, // color + nsGkAtoms::colorInterpolation, // color-interpolation + nsGkAtoms::colorInterpolationFilters, // color-interpolation-filters + nsGkAtoms::cursor, // cursor + nsGkAtoms::cx, // cx + nsGkAtoms::cy, // cy + nsGkAtoms::d, // d + // descent + nsGkAtoms::diffuseConstant, // diffuseConstant + nsGkAtoms::direction, // direction + nsGkAtoms::display, // display + nsGkAtoms::divisor, // divisor + nsGkAtoms::dominant_baseline, // dominant-baseline + nsGkAtoms::dur, // dur + nsGkAtoms::dx, // dx + nsGkAtoms::dy, // dy + nsGkAtoms::edgeMode, // edgeMode + nsGkAtoms::elevation, // elevation + // enable-background + nsGkAtoms::end, // end + nsGkAtoms::fill, // fill + nsGkAtoms::fill_opacity, // fill-opacity + nsGkAtoms::fill_rule, // fill-rule + nsGkAtoms::filter, // filter + nsGkAtoms::filterUnits, // filterUnits + nsGkAtoms::flood_color, // flood-color + nsGkAtoms::flood_opacity, // flood-opacity + // XXX focusable + nsGkAtoms::font, // font + nsGkAtoms::font_family, // font-family + nsGkAtoms::font_size, // font-size + nsGkAtoms::font_size_adjust, // font-size-adjust + nsGkAtoms::font_stretch, // font-stretch + nsGkAtoms::font_style, // font-style + nsGkAtoms::font_variant, // font-variant + nsGkAtoms::fontWeight, // font-weight + nsGkAtoms::format, // format + nsGkAtoms::from, // from + nsGkAtoms::fx, // fx + nsGkAtoms::fy, // fy + // g1 + // g2 + // glyph-name + // glyphRef + // glyph-orientation-horizontal + // glyph-orientation-vertical + nsGkAtoms::gradientTransform, // gradientTransform + nsGkAtoms::gradientUnits, // gradientUnits + nsGkAtoms::height, // height + nsGkAtoms::href, + // horiz-adv-x + // horiz-origin-x + // horiz-origin-y + nsGkAtoms::id, // id + // ideographic + nsGkAtoms::image_rendering, // image-rendering + nsGkAtoms::in, // in + nsGkAtoms::in2, // in2 + nsGkAtoms::intercept, // intercept + // k + nsGkAtoms::k1, // k1 + nsGkAtoms::k2, // k2 + nsGkAtoms::k3, // k3 + nsGkAtoms::k4, // k4 + // kerning + nsGkAtoms::kernelMatrix, // kernelMatrix + nsGkAtoms::kernelUnitLength, // kernelUnitLength + nsGkAtoms::keyPoints, // keyPoints + nsGkAtoms::keySplines, // keySplines + nsGkAtoms::keyTimes, // keyTimes + nsGkAtoms::lang, // lang + // lengthAdjust + nsGkAtoms::letter_spacing, // letter-spacing + nsGkAtoms::lighting_color, // lighting-color + nsGkAtoms::limitingConeAngle, // limitingConeAngle + // local + nsGkAtoms::marker, // marker + nsGkAtoms::marker_end, // marker-end + nsGkAtoms::marker_mid, // marker-mid + nsGkAtoms::marker_start, // marker-start + nsGkAtoms::markerHeight, // markerHeight + nsGkAtoms::markerUnits, // markerUnits + nsGkAtoms::markerWidth, // markerWidth + nsGkAtoms::mask, // mask + nsGkAtoms::maskContentUnits, // maskContentUnits + nsGkAtoms::maskUnits, // maskUnits + // mathematical + nsGkAtoms::max, // max + nsGkAtoms::media, // media + nsGkAtoms::method, // method + nsGkAtoms::min, // min + nsGkAtoms::mode, // mode + nsGkAtoms::name, // name + nsGkAtoms::numOctaves, // numOctaves + nsGkAtoms::offset, // offset + nsGkAtoms::opacity, // opacity + nsGkAtoms::_operator, // operator + nsGkAtoms::order, // order + nsGkAtoms::orient, // orient + nsGkAtoms::orientation, // orientation + // origin + // overline-position + // overline-thickness + nsGkAtoms::overflow, // overflow + // panose-1 + nsGkAtoms::path, // path + nsGkAtoms::pathLength, // pathLength + nsGkAtoms::patternContentUnits, // patternContentUnits + nsGkAtoms::patternTransform, // patternTransform + nsGkAtoms::patternUnits, // patternUnits + nsGkAtoms::pointer_events, // pointer-events XXX is this safe? + nsGkAtoms::points, // points + nsGkAtoms::pointsAtX, // pointsAtX + nsGkAtoms::pointsAtY, // pointsAtY + nsGkAtoms::pointsAtZ, // pointsAtZ + nsGkAtoms::preserveAlpha, // preserveAlpha + nsGkAtoms::preserveAspectRatio, // preserveAspectRatio + nsGkAtoms::primitiveUnits, // primitiveUnits + nsGkAtoms::r, // r + nsGkAtoms::radius, // radius + nsGkAtoms::refX, // refX + nsGkAtoms::refY, // refY + nsGkAtoms::repeatCount, // repeatCount + nsGkAtoms::repeatDur, // repeatDur + nsGkAtoms::requiredExtensions, // requiredExtensions + nsGkAtoms::requiredFeatures, // requiredFeatures + nsGkAtoms::restart, // restart + nsGkAtoms::result, // result + nsGkAtoms::rotate, // rotate + nsGkAtoms::rx, // rx + nsGkAtoms::ry, // ry + nsGkAtoms::scale, // scale + nsGkAtoms::seed, // seed + nsGkAtoms::shape_rendering, // shape-rendering + nsGkAtoms::slope, // slope + nsGkAtoms::spacing, // spacing + nsGkAtoms::specularConstant, // specularConstant + nsGkAtoms::specularExponent, // specularExponent + nsGkAtoms::spreadMethod, // spreadMethod + nsGkAtoms::startOffset, // startOffset + nsGkAtoms::stdDeviation, // stdDeviation + // stemh + // stemv + nsGkAtoms::stitchTiles, // stitchTiles + nsGkAtoms::stop_color, // stop-color + nsGkAtoms::stop_opacity, // stop-opacity + // strikethrough-position + // strikethrough-thickness + nsGkAtoms::string, // string + nsGkAtoms::stroke, // stroke + nsGkAtoms::stroke_dasharray, // stroke-dasharray + nsGkAtoms::stroke_dashoffset, // stroke-dashoffset + nsGkAtoms::stroke_linecap, // stroke-linecap + nsGkAtoms::stroke_linejoin, // stroke-linejoin + nsGkAtoms::stroke_miterlimit, // stroke-miterlimit + nsGkAtoms::stroke_opacity, // stroke-opacity + nsGkAtoms::stroke_width, // stroke-width + nsGkAtoms::surfaceScale, // surfaceScale + nsGkAtoms::systemLanguage, // systemLanguage + nsGkAtoms::tableValues, // tableValues + nsGkAtoms::target, // target + nsGkAtoms::targetX, // targetX + nsGkAtoms::targetY, // targetY + nsGkAtoms::text_anchor, // text-anchor + nsGkAtoms::text_decoration, // text-decoration + // textLength + nsGkAtoms::text_rendering, // text-rendering + nsGkAtoms::title, // title + nsGkAtoms::to, // to + nsGkAtoms::transform, // transform + nsGkAtoms::transform_origin, // transform-origin + nsGkAtoms::type, // type + // u1 + // u2 + // underline-position + // underline-thickness + // unicode + nsGkAtoms::unicode_bidi, // unicode-bidi + // unicode-range + // units-per-em + // v-alphabetic + // v-hanging + // v-ideographic + // v-mathematical + nsGkAtoms::values, // values + nsGkAtoms::vector_effect, // vector-effect + // vert-adv-y + // vert-origin-x + // vert-origin-y + nsGkAtoms::viewBox, // viewBox + nsGkAtoms::viewTarget, // viewTarget + nsGkAtoms::visibility, // visibility + nsGkAtoms::width, // width + // widths + nsGkAtoms::word_spacing, // word-spacing + nsGkAtoms::writing_mode, // writing-mode + nsGkAtoms::x, // x + // x-height + nsGkAtoms::x1, // x1 + nsGkAtoms::x2, // x2 + nsGkAtoms::xChannelSelector, // xChannelSelector + nsGkAtoms::y, // y + nsGkAtoms::y1, // y1 + nsGkAtoms::y2, // y2 + nsGkAtoms::yChannelSelector, // yChannelSelector + nsGkAtoms::z, // z + nsGkAtoms::zoomAndPan, // zoomAndPan + nullptr}; + +constexpr const nsStaticAtom* const kURLAttributesSVG[] = {nsGkAtoms::href, + nullptr}; + +static_assert(AllOf(std::begin(kURLAttributesSVG), std::end(kURLAttributesSVG), + [](auto aURLAttributeSVG) { + return AnyOf(std::begin(kAttributesSVG), + std::end(kAttributesSVG), + [&](auto aAttributeSVG) { + return aAttributeSVG == aURLAttributeSVG; + }); + })); + +const nsStaticAtom* const kElementsMathML[] = { + nsGkAtoms::abs_, // abs + nsGkAtoms::_and, // and + nsGkAtoms::annotation_, // annotation + nsGkAtoms::annotation_xml_, // annotation-xml + nsGkAtoms::apply_, // apply + nsGkAtoms::approx_, // approx + nsGkAtoms::arccos_, // arccos + nsGkAtoms::arccosh_, // arccosh + nsGkAtoms::arccot_, // arccot + nsGkAtoms::arccoth_, // arccoth + nsGkAtoms::arccsc_, // arccsc + nsGkAtoms::arccsch_, // arccsch + nsGkAtoms::arcsec_, // arcsec + nsGkAtoms::arcsech_, // arcsech + nsGkAtoms::arcsin_, // arcsin + nsGkAtoms::arcsinh_, // arcsinh + nsGkAtoms::arctan_, // arctan + nsGkAtoms::arctanh_, // arctanh + nsGkAtoms::arg_, // arg + nsGkAtoms::bind_, // bind + nsGkAtoms::bvar_, // bvar + nsGkAtoms::card_, // card + nsGkAtoms::cartesianproduct_, // cartesianproduct + nsGkAtoms::cbytes_, // cbytes + nsGkAtoms::ceiling, // ceiling + nsGkAtoms::cerror_, // cerror + nsGkAtoms::ci_, // ci + nsGkAtoms::cn_, // cn + nsGkAtoms::codomain_, // codomain + nsGkAtoms::complexes_, // complexes + nsGkAtoms::compose_, // compose + nsGkAtoms::condition_, // condition + nsGkAtoms::conjugate_, // conjugate + nsGkAtoms::cos_, // cos + nsGkAtoms::cosh_, // cosh + nsGkAtoms::cot_, // cot + nsGkAtoms::coth_, // coth + nsGkAtoms::cs_, // cs + nsGkAtoms::csc_, // csc + nsGkAtoms::csch_, // csch + nsGkAtoms::csymbol_, // csymbol + nsGkAtoms::curl_, // curl + nsGkAtoms::declare, // declare + nsGkAtoms::degree_, // degree + nsGkAtoms::determinant_, // determinant + nsGkAtoms::diff_, // diff + nsGkAtoms::divergence_, // divergence + nsGkAtoms::divide_, // divide + nsGkAtoms::domain_, // domain + nsGkAtoms::domainofapplication_, // domainofapplication + nsGkAtoms::el, // el + nsGkAtoms::emptyset_, // emptyset + nsGkAtoms::eq_, // eq + nsGkAtoms::equivalent_, // equivalent + nsGkAtoms::eulergamma_, // eulergamma + nsGkAtoms::exists_, // exists + nsGkAtoms::exp_, // exp + nsGkAtoms::exponentiale_, // exponentiale + nsGkAtoms::factorial_, // factorial + nsGkAtoms::factorof_, // factorof + nsGkAtoms::_false, // false + nsGkAtoms::floor, // floor + nsGkAtoms::fn_, // fn + nsGkAtoms::forall_, // forall + nsGkAtoms::gcd_, // gcd + nsGkAtoms::geq_, // geq + nsGkAtoms::grad, // grad + nsGkAtoms::gt_, // gt + nsGkAtoms::ident_, // ident + nsGkAtoms::image, // image + nsGkAtoms::imaginary_, // imaginary + nsGkAtoms::imaginaryi_, // imaginaryi + nsGkAtoms::implies_, // implies + nsGkAtoms::in, // in + nsGkAtoms::infinity, // infinity + nsGkAtoms::int_, // int + nsGkAtoms::integers_, // integers + nsGkAtoms::intersect_, // intersect + nsGkAtoms::interval_, // interval + nsGkAtoms::inverse_, // inverse + nsGkAtoms::lambda_, // lambda + nsGkAtoms::laplacian_, // laplacian + nsGkAtoms::lcm_, // lcm + nsGkAtoms::leq_, // leq + nsGkAtoms::limit_, // limit + nsGkAtoms::list_, // list + nsGkAtoms::ln_, // ln + nsGkAtoms::log_, // log + nsGkAtoms::logbase_, // logbase + nsGkAtoms::lowlimit_, // lowlimit + nsGkAtoms::lt_, // lt + nsGkAtoms::maction_, // maction + nsGkAtoms::maligngroup_, // maligngroup + nsGkAtoms::malignmark_, // malignmark + nsGkAtoms::math, // math + nsGkAtoms::matrix, // matrix + nsGkAtoms::matrixrow_, // matrixrow + nsGkAtoms::max, // max + nsGkAtoms::mean_, // mean + nsGkAtoms::median_, // median + nsGkAtoms::menclose_, // menclose + nsGkAtoms::merror_, // merror + nsGkAtoms::mfrac_, // mfrac + nsGkAtoms::mglyph_, // mglyph + nsGkAtoms::mi_, // mi + nsGkAtoms::min, // min + nsGkAtoms::minus_, // minus + nsGkAtoms::mlabeledtr_, // mlabeledtr + nsGkAtoms::mlongdiv_, // mlongdiv + nsGkAtoms::mmultiscripts_, // mmultiscripts + nsGkAtoms::mn_, // mn + nsGkAtoms::mo_, // mo + nsGkAtoms::mode, // mode + nsGkAtoms::moment_, // moment + nsGkAtoms::momentabout_, // momentabout + nsGkAtoms::mover_, // mover + nsGkAtoms::mpadded_, // mpadded + nsGkAtoms::mphantom_, // mphantom + nsGkAtoms::mprescripts_, // mprescripts + nsGkAtoms::mroot_, // mroot + nsGkAtoms::mrow_, // mrow + nsGkAtoms::ms_, // ms + nsGkAtoms::mscarries_, // mscarries + nsGkAtoms::mscarry_, // mscarry + nsGkAtoms::msgroup_, // msgroup + nsGkAtoms::msline_, // msline + nsGkAtoms::mspace_, // mspace + nsGkAtoms::msqrt_, // msqrt + nsGkAtoms::msrow_, // msrow + nsGkAtoms::mstack_, // mstack + nsGkAtoms::mstyle_, // mstyle + nsGkAtoms::msub_, // msub + nsGkAtoms::msubsup_, // msubsup + nsGkAtoms::msup_, // msup + nsGkAtoms::mtable_, // mtable + nsGkAtoms::mtd_, // mtd + nsGkAtoms::mtext_, // mtext + nsGkAtoms::mtr_, // mtr + nsGkAtoms::munder_, // munder + nsGkAtoms::munderover_, // munderover + nsGkAtoms::naturalnumbers_, // naturalnumbers + nsGkAtoms::neq_, // neq + nsGkAtoms::none, // none + nsGkAtoms::_not, // not + nsGkAtoms::notanumber_, // notanumber + nsGkAtoms::note_, // note + nsGkAtoms::notin_, // notin + nsGkAtoms::notprsubset_, // notprsubset + nsGkAtoms::notsubset_, // notsubset + nsGkAtoms::_or, // or + nsGkAtoms::otherwise, // otherwise + nsGkAtoms::outerproduct_, // outerproduct + nsGkAtoms::partialdiff_, // partialdiff + nsGkAtoms::pi_, // pi + nsGkAtoms::piece_, // piece + nsGkAtoms::piecewise_, // piecewise + nsGkAtoms::plus_, // plus + nsGkAtoms::power_, // power + nsGkAtoms::primes_, // primes + nsGkAtoms::product_, // product + nsGkAtoms::prsubset_, // prsubset + nsGkAtoms::quotient_, // quotient + nsGkAtoms::rationals_, // rationals + nsGkAtoms::real_, // real + nsGkAtoms::reals_, // reals + nsGkAtoms::reln_, // reln + nsGkAtoms::rem, // rem + nsGkAtoms::root_, // root + nsGkAtoms::scalarproduct_, // scalarproduct + nsGkAtoms::sdev_, // sdev + nsGkAtoms::sec_, // sec + nsGkAtoms::sech_, // sech + nsGkAtoms::selector_, // selector + nsGkAtoms::semantics_, // semantics + nsGkAtoms::sep_, // sep + nsGkAtoms::set, // set + nsGkAtoms::setdiff_, // setdiff + nsGkAtoms::share_, // share + nsGkAtoms::sin_, // sin + nsGkAtoms::sinh_, // sinh + nsGkAtoms::subset_, // subset + nsGkAtoms::sum, // sum + nsGkAtoms::tan_, // tan + nsGkAtoms::tanh_, // tanh + nsGkAtoms::tendsto_, // tendsto + nsGkAtoms::times_, // times + nsGkAtoms::transpose_, // transpose + nsGkAtoms::_true, // true + nsGkAtoms::union_, // union + nsGkAtoms::uplimit_, // uplimit + nsGkAtoms::variance_, // variance + nsGkAtoms::vector_, // vector + nsGkAtoms::vectorproduct_, // vectorproduct + nsGkAtoms::xor_, // xor + nullptr}; + +const nsStaticAtom* const kAttributesMathML[] = { + nsGkAtoms::accent_, // accent + nsGkAtoms::accentunder_, // accentunder + nsGkAtoms::actiontype_, // actiontype + nsGkAtoms::align, // align + nsGkAtoms::alignmentscope_, // alignmentscope + nsGkAtoms::alt, // alt + nsGkAtoms::altimg_, // altimg + nsGkAtoms::altimg_height_, // altimg-height + nsGkAtoms::altimg_valign_, // altimg-valign + nsGkAtoms::altimg_width_, // altimg-width + nsGkAtoms::background, // background + nsGkAtoms::base, // base + nsGkAtoms::bevelled_, // bevelled + nsGkAtoms::cd_, // cd + nsGkAtoms::cdgroup_, // cdgroup + nsGkAtoms::charalign_, // charalign + nsGkAtoms::close, // close + nsGkAtoms::closure_, // closure + nsGkAtoms::color, // color + nsGkAtoms::columnalign_, // columnalign + nsGkAtoms::columnalignment_, // columnalignment + nsGkAtoms::columnlines_, // columnlines + nsGkAtoms::columnspacing_, // columnspacing + nsGkAtoms::columnspan_, // columnspan + nsGkAtoms::columnwidth_, // columnwidth + nsGkAtoms::crossout_, // crossout + nsGkAtoms::decimalpoint_, // decimalpoint + nsGkAtoms::definitionURL_, // definitionURL + nsGkAtoms::denomalign_, // denomalign + nsGkAtoms::depth_, // depth + nsGkAtoms::dir, // dir + nsGkAtoms::display, // display + nsGkAtoms::displaystyle_, // displaystyle + nsGkAtoms::edge_, // edge + nsGkAtoms::encoding, // encoding + nsGkAtoms::equalcolumns_, // equalcolumns + nsGkAtoms::equalrows_, // equalrows + nsGkAtoms::fence_, // fence + nsGkAtoms::fontfamily_, // fontfamily + nsGkAtoms::fontsize_, // fontsize + nsGkAtoms::fontstyle_, // fontstyle + nsGkAtoms::fontweight_, // fontweight + nsGkAtoms::form, // form + nsGkAtoms::frame, // frame + nsGkAtoms::framespacing_, // framespacing + nsGkAtoms::groupalign_, // groupalign + nsGkAtoms::height, // height + nsGkAtoms::href, // href + nsGkAtoms::id, // id + nsGkAtoms::indentalign_, // indentalign + nsGkAtoms::indentalignfirst_, // indentalignfirst + nsGkAtoms::indentalignlast_, // indentalignlast + nsGkAtoms::indentshift_, // indentshift + nsGkAtoms::indentshiftfirst_, // indentshiftfirst + nsGkAtoms::indenttarget_, // indenttarget + nsGkAtoms::index, // index + nsGkAtoms::integer, // integer + nsGkAtoms::largeop_, // largeop + nsGkAtoms::length, // length + nsGkAtoms::linebreak_, // linebreak + nsGkAtoms::linebreakmultchar_, // linebreakmultchar + nsGkAtoms::linebreakstyle_, // linebreakstyle + nsGkAtoms::linethickness_, // linethickness + nsGkAtoms::location_, // location + nsGkAtoms::longdivstyle_, // longdivstyle + nsGkAtoms::lquote_, // lquote + nsGkAtoms::lspace_, // lspace + nsGkAtoms::ltr, // ltr + nsGkAtoms::mathbackground_, // mathbackground + nsGkAtoms::mathcolor_, // mathcolor + nsGkAtoms::mathsize_, // mathsize + nsGkAtoms::mathvariant_, // mathvariant + nsGkAtoms::maxsize_, // maxsize + nsGkAtoms::minlabelspacing_, // minlabelspacing + nsGkAtoms::minsize_, // minsize + nsGkAtoms::movablelimits_, // movablelimits + nsGkAtoms::msgroup_, // msgroup + nsGkAtoms::name, // name + nsGkAtoms::newline, // newline + nsGkAtoms::notation_, // notation + nsGkAtoms::numalign_, // numalign + nsGkAtoms::number, // number + nsGkAtoms::open, // open + nsGkAtoms::order, // order + nsGkAtoms::other, // other + nsGkAtoms::overflow, // overflow + nsGkAtoms::position, // position + nsGkAtoms::role, // role + nsGkAtoms::rowalign_, // rowalign + nsGkAtoms::rowlines_, // rowlines + nsGkAtoms::rowspacing_, // rowspacing + nsGkAtoms::rowspan, // rowspan + nsGkAtoms::rquote_, // rquote + nsGkAtoms::rspace_, // rspace + nsGkAtoms::schemaLocation_, // schemaLocation + nsGkAtoms::scriptlevel_, // scriptlevel + nsGkAtoms::scriptminsize_, // scriptminsize + nsGkAtoms::scriptsize_, // scriptsize + nsGkAtoms::scriptsizemultiplier_, // scriptsizemultiplier + nsGkAtoms::selection_, // selection + nsGkAtoms::separator_, // separator + nsGkAtoms::separators_, // separators + nsGkAtoms::shift_, // shift + nsGkAtoms::side_, // side + nsGkAtoms::src, // src + nsGkAtoms::stackalign_, // stackalign + nsGkAtoms::stretchy_, // stretchy + nsGkAtoms::subscriptshift_, // subscriptshift + nsGkAtoms::superscriptshift_, // superscriptshift + nsGkAtoms::symmetric_, // symmetric + nsGkAtoms::type, // type + nsGkAtoms::voffset_, // voffset + nsGkAtoms::width, // width + nsGkAtoms::xref_, // xref + nullptr}; + +const nsStaticAtom* const kURLAttributesMathML[] = { + // clang-format off + nsGkAtoms::href, + nsGkAtoms::src, + nsGkAtoms::cdgroup_, + nsGkAtoms::altimg_, + nsGkAtoms::definitionURL_, + nullptr + // clang-format on +}; + +// https://wicg.github.io/sanitizer-api/#baseline-attribute-allow-list +constexpr const nsStaticAtom* const kBaselineAttributeAllowlist[] = { + // clang-format off + nsGkAtoms::abbr, + nsGkAtoms::accept, + nsGkAtoms::acceptcharset, + nsGkAtoms::charset, + nsGkAtoms::accesskey, + nsGkAtoms::action, + nsGkAtoms::align, + nsGkAtoms::alink, + nsGkAtoms::allow, + nsGkAtoms::allowfullscreen, + // nsGkAtoms::allowpaymentrequest, + nsGkAtoms::alt, + nsGkAtoms::anchor, + nsGkAtoms::archive, + nsGkAtoms::as, + nsGkAtoms::async, + nsGkAtoms::autocapitalize, + nsGkAtoms::autocomplete, + // nsGkAtoms::autocorrect, + nsGkAtoms::autofocus, + // nsGkAtoms::autopictureinpicture, + nsGkAtoms::autoplay, + nsGkAtoms::axis, + nsGkAtoms::background, + nsGkAtoms::behavior, + nsGkAtoms::bgcolor, + nsGkAtoms::border, + nsGkAtoms::bordercolor, + nsGkAtoms::capture, + nsGkAtoms::cellpadding, + nsGkAtoms::cellspacing, + // nsGkAtoms::challenge, + nsGkAtoms::_char, + nsGkAtoms::charoff, + nsGkAtoms::charset, + nsGkAtoms::checked, + nsGkAtoms::cite, + nsGkAtoms::_class, + nsGkAtoms::classid, + nsGkAtoms::clear, + nsGkAtoms::code, + nsGkAtoms::codebase, + nsGkAtoms::codetype, + nsGkAtoms::color, + nsGkAtoms::cols, + nsGkAtoms::colspan, + nsGkAtoms::compact, + nsGkAtoms::content, + nsGkAtoms::contenteditable, + nsGkAtoms::controls, + // nsGkAtoms::controlslist, + // nsGkAtoms::conversiondestination, + nsGkAtoms::coords, + nsGkAtoms::crossorigin, + nsGkAtoms::csp, + nsGkAtoms::data, + nsGkAtoms::datetime, + nsGkAtoms::declare, + nsGkAtoms::decoding, + nsGkAtoms::_default, + nsGkAtoms::defer, + nsGkAtoms::dir, + nsGkAtoms::direction, + // nsGkAtoms::dirname, + nsGkAtoms::disabled, + // nsGkAtoms::disablepictureinpicture, + // nsGkAtoms::disableremoteplayback, + // nsGkAtoms::disallowdocumentaccess, + nsGkAtoms::download, + nsGkAtoms::draggable, + // nsGkAtoms::elementtiming, + nsGkAtoms::enctype, + nsGkAtoms::end, + nsGkAtoms::enterkeyhint, + nsGkAtoms::event, + nsGkAtoms::exportparts, + nsGkAtoms::face, + nsGkAtoms::_for, + nsGkAtoms::form, + nsGkAtoms::formaction, + nsGkAtoms::formenctype, + nsGkAtoms::formmethod, + nsGkAtoms::formnovalidate, + nsGkAtoms::formtarget, + nsGkAtoms::frame, + nsGkAtoms::frameborder, + nsGkAtoms::headers, + nsGkAtoms::height, + nsGkAtoms::hidden, + nsGkAtoms::high, + nsGkAtoms::href, + nsGkAtoms::hreflang, + // nsGkAtoms::hreftranslate, + nsGkAtoms::hspace, + nsGkAtoms::http, + // nsGkAtoms::equiv, + nsGkAtoms::id, + nsGkAtoms::imagesizes, + nsGkAtoms::imagesrcset, + // nsGkAtoms::importance, + // nsGkAtoms::impressiondata, + // nsGkAtoms::impressionexpiry, + // nsGkAtoms::incremental, + nsGkAtoms::inert, + nsGkAtoms::inputmode, + nsGkAtoms::integrity, + // nsGkAtoms::invisible, + nsGkAtoms::is, + nsGkAtoms::ismap, + // nsGkAtoms::keytype, + nsGkAtoms::kind, + nsGkAtoms::label, + nsGkAtoms::lang, + nsGkAtoms::language, + // nsGkAtoms::latencyhint, + nsGkAtoms::leftmargin, + nsGkAtoms::link, + // nsGkAtoms::list, + nsGkAtoms::loading, + nsGkAtoms::longdesc, + nsGkAtoms::loop, + nsGkAtoms::low, + nsGkAtoms::lowsrc, + nsGkAtoms::manifest, + nsGkAtoms::marginheight, + nsGkAtoms::marginwidth, + nsGkAtoms::max, + nsGkAtoms::maxlength, + // nsGkAtoms::mayscript, + nsGkAtoms::media, + nsGkAtoms::method, + nsGkAtoms::min, + nsGkAtoms::minlength, + nsGkAtoms::multiple, + nsGkAtoms::muted, + nsGkAtoms::name, + nsGkAtoms::nohref, + nsGkAtoms::nomodule, + nsGkAtoms::nonce, + nsGkAtoms::noresize, + nsGkAtoms::noshade, + nsGkAtoms::novalidate, + nsGkAtoms::nowrap, + nsGkAtoms::object, + nsGkAtoms::open, + nsGkAtoms::optimum, + nsGkAtoms::part, + nsGkAtoms::pattern, + nsGkAtoms::ping, + nsGkAtoms::placeholder, + // nsGkAtoms::playsinline, + // nsGkAtoms::policy, + nsGkAtoms::poster, + nsGkAtoms::preload, + // nsGkAtoms::pseudo, + nsGkAtoms::readonly, + nsGkAtoms::referrerpolicy, + nsGkAtoms::rel, + // nsGkAtoms::reportingorigin, + nsGkAtoms::required, + nsGkAtoms::resources, + nsGkAtoms::rev, + nsGkAtoms::reversed, + nsGkAtoms::role, + nsGkAtoms::rows, + nsGkAtoms::rowspan, + nsGkAtoms::rules, + nsGkAtoms::sandbox, + nsGkAtoms::scheme, + nsGkAtoms::scope, + // nsGkAtoms::scopes, + nsGkAtoms::scrollamount, + nsGkAtoms::scrolldelay, + nsGkAtoms::scrolling, + nsGkAtoms::select, + nsGkAtoms::selected, + // nsGkAtoms::shadowroot, + // nsGkAtoms::shadowrootdelegatesfocus, + nsGkAtoms::shape, + nsGkAtoms::size, + nsGkAtoms::sizes, + nsGkAtoms::slot, + nsGkAtoms::span, + nsGkAtoms::spellcheck, + nsGkAtoms::src, + nsGkAtoms::srcdoc, + nsGkAtoms::srclang, + nsGkAtoms::srcset, + nsGkAtoms::standby, + nsGkAtoms::start, + nsGkAtoms::step, + nsGkAtoms::style, + nsGkAtoms::summary, + nsGkAtoms::tabindex, + nsGkAtoms::target, + nsGkAtoms::text, + nsGkAtoms::title, + nsGkAtoms::topmargin, + nsGkAtoms::translate, + nsGkAtoms::truespeed, + // nsGkAtoms::trusttoken, + nsGkAtoms::type, + nsGkAtoms::usemap, + nsGkAtoms::valign, + nsGkAtoms::value, + nsGkAtoms::valuetype, + nsGkAtoms::version, + // nsGkAtoms::virtualkeyboardpolicy, + nsGkAtoms::vlink, + nsGkAtoms::vspace, + nsGkAtoms::webkitdirectory, + nsGkAtoms::width, + nsGkAtoms::wrap, + // clang-format on +}; + +// https://wicg.github.io/sanitizer-api/#baseline-elements +constexpr const nsStaticAtom* const kBaselineElementAllowlist[] = { + nsGkAtoms::a, nsGkAtoms::abbr, nsGkAtoms::acronym, + nsGkAtoms::address, nsGkAtoms::area, nsGkAtoms::article, + nsGkAtoms::aside, nsGkAtoms::audio, nsGkAtoms::b, + nsGkAtoms::basefont, nsGkAtoms::bdi, nsGkAtoms::bdo, + nsGkAtoms::bgsound, nsGkAtoms::big, nsGkAtoms::blockquote, + nsGkAtoms::body, nsGkAtoms::br, nsGkAtoms::button, + nsGkAtoms::canvas, nsGkAtoms::caption, nsGkAtoms::center, + nsGkAtoms::cite, nsGkAtoms::code, nsGkAtoms::col, + nsGkAtoms::colgroup, nsGkAtoms::command, nsGkAtoms::data, + nsGkAtoms::datalist, nsGkAtoms::dd, nsGkAtoms::del, + nsGkAtoms::details, nsGkAtoms::dfn, nsGkAtoms::dialog, + nsGkAtoms::dir, nsGkAtoms::div, nsGkAtoms::dl, + nsGkAtoms::dt, nsGkAtoms::em, nsGkAtoms::fieldset, + nsGkAtoms::figcaption, nsGkAtoms::figure, nsGkAtoms::font, + nsGkAtoms::footer, nsGkAtoms::form, nsGkAtoms::h1, + nsGkAtoms::h2, nsGkAtoms::h3, nsGkAtoms::h4, + nsGkAtoms::h5, nsGkAtoms::h6, nsGkAtoms::head, + nsGkAtoms::header, nsGkAtoms::hgroup, nsGkAtoms::hr, + nsGkAtoms::html, nsGkAtoms::i, nsGkAtoms::image, + nsGkAtoms::img, nsGkAtoms::input, nsGkAtoms::ins, + nsGkAtoms::kbd, nsGkAtoms::keygen, nsGkAtoms::label, + nsGkAtoms::layer, nsGkAtoms::legend, nsGkAtoms::li, + nsGkAtoms::link, nsGkAtoms::listing, nsGkAtoms::main, + nsGkAtoms::map, nsGkAtoms::mark, nsGkAtoms::marquee, + nsGkAtoms::menu, nsGkAtoms::meta, nsGkAtoms::meter, + nsGkAtoms::nav, nsGkAtoms::nobr, nsGkAtoms::ol, + nsGkAtoms::optgroup, nsGkAtoms::option, nsGkAtoms::output, + nsGkAtoms::p, nsGkAtoms::picture, nsGkAtoms::plaintext, + nsGkAtoms::popup, nsGkAtoms::portal, nsGkAtoms::pre, + nsGkAtoms::progress, nsGkAtoms::q, nsGkAtoms::rb, + nsGkAtoms::rp, nsGkAtoms::rt, nsGkAtoms::rtc, + nsGkAtoms::ruby, nsGkAtoms::s, nsGkAtoms::samp, + nsGkAtoms::section, nsGkAtoms::select, nsGkAtoms::selectmenu, + nsGkAtoms::slot, nsGkAtoms::small, nsGkAtoms::source, + nsGkAtoms::span, nsGkAtoms::strike, nsGkAtoms::strong, + nsGkAtoms::style, nsGkAtoms::sub, nsGkAtoms::summary, + nsGkAtoms::sup, nsGkAtoms::table, nsGkAtoms::tbody, + nsGkAtoms::td, nsGkAtoms::_template, nsGkAtoms::textarea, + nsGkAtoms::tfoot, nsGkAtoms::th, nsGkAtoms::thead, + nsGkAtoms::time, nsGkAtoms::title, nsGkAtoms::tr, + nsGkAtoms::track, nsGkAtoms::tt, nsGkAtoms::u, + nsGkAtoms::ul, nsGkAtoms::var, nsGkAtoms::video, + nsGkAtoms::wbr, nsGkAtoms::xmp, +}; + +// https://wicg.github.io/sanitizer-api/#default-configuration +// default configuration's attribute allow list. +// Note: Currently all listed attributes are allowed for every element +// (e.g. they use "*"). +// Compared to kBaselineAttributeAllowlist only deprecated allowpaymentrequest +// attribute is missing. +constexpr const nsStaticAtom* const kDefaultConfigurationAttributeAllowlist[] = + { + nsGkAtoms::abbr, + nsGkAtoms::accept, + nsGkAtoms::acceptcharset, + nsGkAtoms::charset, + nsGkAtoms::accesskey, + nsGkAtoms::action, + nsGkAtoms::align, + nsGkAtoms::alink, + nsGkAtoms::allow, + nsGkAtoms::allowfullscreen, + nsGkAtoms::alt, + nsGkAtoms::anchor, + nsGkAtoms::archive, + nsGkAtoms::as, + nsGkAtoms::async, + nsGkAtoms::autocapitalize, + nsGkAtoms::autocomplete, + // nsGkAtoms::autocorrect, + nsGkAtoms::autofocus, + // nsGkAtoms::autopictureinpicture, + nsGkAtoms::autoplay, + nsGkAtoms::axis, + nsGkAtoms::background, + nsGkAtoms::behavior, + nsGkAtoms::bgcolor, + nsGkAtoms::border, + nsGkAtoms::bordercolor, + nsGkAtoms::capture, + nsGkAtoms::cellpadding, + nsGkAtoms::cellspacing, + // nsGkAtoms::challenge, + nsGkAtoms::_char, + nsGkAtoms::charoff, + nsGkAtoms::charset, + nsGkAtoms::checked, + nsGkAtoms::cite, + nsGkAtoms::_class, + nsGkAtoms::classid, + nsGkAtoms::clear, + nsGkAtoms::code, + nsGkAtoms::codebase, + nsGkAtoms::codetype, + nsGkAtoms::color, + nsGkAtoms::cols, + nsGkAtoms::colspan, + nsGkAtoms::compact, + nsGkAtoms::content, + nsGkAtoms::contenteditable, + nsGkAtoms::controls, + // nsGkAtoms::controlslist, + // nsGkAtoms::conversiondestination, + nsGkAtoms::coords, + nsGkAtoms::crossorigin, + nsGkAtoms::csp, + nsGkAtoms::data, + nsGkAtoms::datetime, + nsGkAtoms::declare, + nsGkAtoms::decoding, + nsGkAtoms::_default, + nsGkAtoms::defer, + nsGkAtoms::dir, + nsGkAtoms::direction, + // nsGkAtoms::dirname, + nsGkAtoms::disabled, + // nsGkAtoms::disablepictureinpicture, + // nsGkAtoms::disableremoteplayback, + // nsGkAtoms::disallowdocumentaccess, + nsGkAtoms::download, + nsGkAtoms::draggable, + // nsGkAtoms::elementtiming, + nsGkAtoms::enctype, + nsGkAtoms::end, + nsGkAtoms::enterkeyhint, + nsGkAtoms::event, + nsGkAtoms::exportparts, + nsGkAtoms::face, + nsGkAtoms::_for, + nsGkAtoms::form, + nsGkAtoms::formaction, + nsGkAtoms::formenctype, + nsGkAtoms::formmethod, + nsGkAtoms::formnovalidate, + nsGkAtoms::formtarget, + nsGkAtoms::frame, + nsGkAtoms::frameborder, + nsGkAtoms::headers, + nsGkAtoms::height, + nsGkAtoms::hidden, + nsGkAtoms::high, + nsGkAtoms::href, + nsGkAtoms::hreflang, + // nsGkAtoms::hreftranslate, + nsGkAtoms::hspace, + nsGkAtoms::http, + // nsGkAtoms::equiv, + nsGkAtoms::id, + nsGkAtoms::imagesizes, + nsGkAtoms::imagesrcset, + // nsGkAtoms::importance, + // nsGkAtoms::impressiondata, + // nsGkAtoms::impressionexpiry, + // nsGkAtoms::incremental, + nsGkAtoms::inert, + nsGkAtoms::inputmode, + nsGkAtoms::integrity, + // nsGkAtoms::invisible, + nsGkAtoms::is, + nsGkAtoms::ismap, + // nsGkAtoms::keytype, + nsGkAtoms::kind, + nsGkAtoms::label, + nsGkAtoms::lang, + nsGkAtoms::language, + // nsGkAtoms::latencyhint, + nsGkAtoms::leftmargin, + nsGkAtoms::link, + // nsGkAtoms::list, + nsGkAtoms::loading, + nsGkAtoms::longdesc, + nsGkAtoms::loop, + nsGkAtoms::low, + nsGkAtoms::lowsrc, + nsGkAtoms::manifest, + nsGkAtoms::marginheight, + nsGkAtoms::marginwidth, + nsGkAtoms::max, + nsGkAtoms::maxlength, + // nsGkAtoms::mayscript, + nsGkAtoms::media, + nsGkAtoms::method, + nsGkAtoms::min, + nsGkAtoms::minlength, + nsGkAtoms::multiple, + nsGkAtoms::muted, + nsGkAtoms::name, + nsGkAtoms::nohref, + nsGkAtoms::nomodule, + nsGkAtoms::nonce, + nsGkAtoms::noresize, + nsGkAtoms::noshade, + nsGkAtoms::novalidate, + nsGkAtoms::nowrap, + nsGkAtoms::object, + nsGkAtoms::open, + nsGkAtoms::optimum, + nsGkAtoms::part, + nsGkAtoms::pattern, + nsGkAtoms::ping, + nsGkAtoms::placeholder, + // nsGkAtoms::playsinline, + // nsGkAtoms::policy, + nsGkAtoms::poster, + nsGkAtoms::preload, + // nsGkAtoms::pseudo, + nsGkAtoms::readonly, + nsGkAtoms::referrerpolicy, + nsGkAtoms::rel, + // nsGkAtoms::reportingorigin, + nsGkAtoms::required, + nsGkAtoms::resources, + nsGkAtoms::rev, + nsGkAtoms::reversed, + nsGkAtoms::role, + nsGkAtoms::rows, + nsGkAtoms::rowspan, + nsGkAtoms::rules, + nsGkAtoms::sandbox, + nsGkAtoms::scheme, + nsGkAtoms::scope, + // nsGkAtoms::scopes, + nsGkAtoms::scrollamount, + nsGkAtoms::scrolldelay, + nsGkAtoms::scrolling, + nsGkAtoms::select, + nsGkAtoms::selected, + // nsGkAtoms::shadowroot, + // nsGkAtoms::shadowrootdelegatesfocus, + nsGkAtoms::shape, + nsGkAtoms::size, + nsGkAtoms::sizes, + nsGkAtoms::slot, + nsGkAtoms::span, + nsGkAtoms::spellcheck, + nsGkAtoms::src, + nsGkAtoms::srcdoc, + nsGkAtoms::srclang, + nsGkAtoms::srcset, + nsGkAtoms::standby, + nsGkAtoms::start, + nsGkAtoms::step, + nsGkAtoms::style, + nsGkAtoms::summary, + nsGkAtoms::tabindex, + nsGkAtoms::target, + nsGkAtoms::text, + nsGkAtoms::title, + nsGkAtoms::topmargin, + nsGkAtoms::translate, + nsGkAtoms::truespeed, + // nsGkAtoms::trusttoken, + nsGkAtoms::type, + nsGkAtoms::usemap, + nsGkAtoms::valign, + nsGkAtoms::value, + nsGkAtoms::valuetype, + nsGkAtoms::version, + // nsGkAtoms::virtualkeyboardpolicy, + nsGkAtoms::vlink, + nsGkAtoms::vspace, + nsGkAtoms::webkitdirectory, + nsGkAtoms::width, + nsGkAtoms::wrap, +}; + +// https://wicg.github.io/sanitizer-api/#default-configuration +// default configuration's element allow list. +constexpr const nsStaticAtom* const kDefaultConfigurationElementAllowlist[] = { + nsGkAtoms::a, nsGkAtoms::abbr, nsGkAtoms::acronym, + nsGkAtoms::address, nsGkAtoms::area, nsGkAtoms::article, + nsGkAtoms::aside, nsGkAtoms::audio, nsGkAtoms::b, + nsGkAtoms::bdi, nsGkAtoms::bdo, nsGkAtoms::bgsound, + nsGkAtoms::big, nsGkAtoms::blockquote, nsGkAtoms::body, + nsGkAtoms::br, nsGkAtoms::button, nsGkAtoms::canvas, + nsGkAtoms::caption, nsGkAtoms::center, nsGkAtoms::cite, + nsGkAtoms::code, nsGkAtoms::col, nsGkAtoms::colgroup, + nsGkAtoms::datalist, nsGkAtoms::dd, nsGkAtoms::del, + nsGkAtoms::details, nsGkAtoms::dfn, nsGkAtoms::dialog, + nsGkAtoms::dir, nsGkAtoms::div, nsGkAtoms::dl, + nsGkAtoms::dt, nsGkAtoms::em, nsGkAtoms::fieldset, + nsGkAtoms::figcaption, nsGkAtoms::figure, nsGkAtoms::font, + nsGkAtoms::footer, nsGkAtoms::form, nsGkAtoms::h1, + nsGkAtoms::h2, nsGkAtoms::h3, nsGkAtoms::h4, + nsGkAtoms::h5, nsGkAtoms::h6, nsGkAtoms::head, + nsGkAtoms::header, nsGkAtoms::hgroup, nsGkAtoms::hr, + nsGkAtoms::html, nsGkAtoms::i, nsGkAtoms::img, + nsGkAtoms::input, nsGkAtoms::ins, nsGkAtoms::kbd, + nsGkAtoms::keygen, nsGkAtoms::label, nsGkAtoms::layer, + nsGkAtoms::legend, nsGkAtoms::li, nsGkAtoms::link, + nsGkAtoms::listing, nsGkAtoms::main, nsGkAtoms::map, + nsGkAtoms::mark, nsGkAtoms::marquee, nsGkAtoms::menu, + nsGkAtoms::meta, nsGkAtoms::meter, nsGkAtoms::nav, + nsGkAtoms::nobr, nsGkAtoms::ol, nsGkAtoms::optgroup, + nsGkAtoms::option, nsGkAtoms::output, nsGkAtoms::p, + nsGkAtoms::picture, nsGkAtoms::popup, nsGkAtoms::pre, + nsGkAtoms::progress, nsGkAtoms::q, nsGkAtoms::rb, + nsGkAtoms::rp, nsGkAtoms::rt, nsGkAtoms::rtc, + nsGkAtoms::ruby, nsGkAtoms::s, nsGkAtoms::samp, + nsGkAtoms::section, nsGkAtoms::select, nsGkAtoms::selectmenu, + nsGkAtoms::small, nsGkAtoms::source, nsGkAtoms::span, + nsGkAtoms::strike, nsGkAtoms::strong, nsGkAtoms::style, + nsGkAtoms::sub, nsGkAtoms::summary, nsGkAtoms::sup, + nsGkAtoms::table, nsGkAtoms::tbody, nsGkAtoms::td, + nsGkAtoms::tfoot, nsGkAtoms::th, nsGkAtoms::thead, + nsGkAtoms::time, nsGkAtoms::tr, nsGkAtoms::track, + nsGkAtoms::tt, nsGkAtoms::u, nsGkAtoms::ul, + nsGkAtoms::var, nsGkAtoms::video, nsGkAtoms::wbr, +}; + +nsTreeSanitizer::AtomsTable* nsTreeSanitizer::sElementsHTML = nullptr; +nsTreeSanitizer::AtomsTable* nsTreeSanitizer::sAttributesHTML = nullptr; +nsTreeSanitizer::AtomsTable* nsTreeSanitizer::sPresAttributesHTML = nullptr; +nsTreeSanitizer::AtomsTable* nsTreeSanitizer::sElementsSVG = nullptr; +nsTreeSanitizer::AtomsTable* nsTreeSanitizer::sAttributesSVG = nullptr; +nsTreeSanitizer::AtomsTable* nsTreeSanitizer::sElementsMathML = nullptr; +nsTreeSanitizer::AtomsTable* nsTreeSanitizer::sAttributesMathML = nullptr; +nsTreeSanitizer::AtomsTable* nsTreeSanitizer::sBaselineAttributeAllowlist = + nullptr; +nsTreeSanitizer::AtomsTable* nsTreeSanitizer::sBaselineElementAllowlist = + nullptr; +nsTreeSanitizer::AtomsTable* + nsTreeSanitizer::sDefaultConfigurationAttributeAllowlist = nullptr; +nsTreeSanitizer::AtomsTable* + nsTreeSanitizer::sDefaultConfigurationElementAllowlist = nullptr; +nsIPrincipal* nsTreeSanitizer::sNullPrincipal = nullptr; + +nsTreeSanitizer::nsTreeSanitizer(uint32_t aFlags) + : mAllowStyles(aFlags & nsIParserUtils::SanitizerAllowStyle), + mAllowComments(aFlags & nsIParserUtils::SanitizerAllowComments), + mDropNonCSSPresentation(aFlags & + nsIParserUtils::SanitizerDropNonCSSPresentation), + mDropForms(aFlags & nsIParserUtils::SanitizerDropForms), + mCidEmbedsOnly(aFlags & nsIParserUtils::SanitizerCidEmbedsOnly), + mDropMedia(aFlags & nsIParserUtils::SanitizerDropMedia), + mFullDocument(false), + mLogRemovals(aFlags & nsIParserUtils::SanitizerLogRemovals) { + if (mCidEmbedsOnly) { + // Sanitizing styles for external references is not supported. + mAllowStyles = false; + } + + if (!sElementsHTML) { + // Initialize lazily to avoid having to initialize at all if the user + // doesn't paste HTML or load feeds. + InitializeStatics(); + } +} + +bool nsTreeSanitizer::MustFlatten(int32_t aNamespace, nsAtom* aLocal) { + if (mIsForSanitizerAPI) { + return MustFlattenForSanitizerAPI(aNamespace, aLocal); + } + + if (aNamespace == kNameSpaceID_XHTML) { + if (mDropNonCSSPresentation && + (nsGkAtoms::font == aLocal || nsGkAtoms::center == aLocal)) { + return true; + } + if (mDropForms && + (nsGkAtoms::form == aLocal || nsGkAtoms::input == aLocal || + nsGkAtoms::option == aLocal || nsGkAtoms::optgroup == aLocal)) { + return true; + } + if (mFullDocument && + (nsGkAtoms::title == aLocal || nsGkAtoms::html == aLocal || + nsGkAtoms::head == aLocal || nsGkAtoms::body == aLocal)) { + return false; + } + if (nsGkAtoms::_template == aLocal) { + return false; + } + return !sElementsHTML->Contains(aLocal); + } + if (aNamespace == kNameSpaceID_SVG) { + if (mCidEmbedsOnly || mDropMedia) { + // Sanitizing CSS-based URL references inside SVG presentational + // attributes is not supported, so flattening for cid: embed case. + return true; + } + return !sElementsSVG->Contains(aLocal); + } + if (aNamespace == kNameSpaceID_MathML) { + return !sElementsMathML->Contains(aLocal); + } + return true; +} + +bool nsTreeSanitizer::MustFlattenForSanitizerAPI(int32_t aNamespace, + nsAtom* aLocal) { + // This implements everything in + // https://wicg.github.io/sanitizer-api/#sanitize-action-for-an-element that + // is supposed to be blocked. + + // Step 6. If element matches any name in config["blockElements"]: Return + // block. + if (mReplaceWithChildrenElements && + MatchesElementName(*mReplaceWithChildrenElements, aNamespace, aLocal)) { + return true; + } + + // Step 7. Let allow list be null. + // Step 8. If "allowElements" exists in config: + // Step 8.1. Then : Set allow list to config["allowElements"]. + if (mElements) { + // Step 9. If element does not match any name in allow list: + // Return block. + if (!mElements->Contains(ElementName(aNamespace, aLocal))) { + return true; + } + } else { + // Step 8.2. Otherwise: Set allow list to the default configuration's + // element allow list. + + // Step 9. If element does not match any name in allow list: + // Return block. + + // The default configuration only contains HTML elements, so we can + // reject everything else. + if (aNamespace != kNameSpaceID_XHTML || + !sDefaultConfigurationElementAllowlist->Contains(aLocal)) { + return true; + } + } + + // Step 10. Return keep. + return false; +} + +bool nsTreeSanitizer::IsURL(const nsStaticAtom* const* aURLs, + nsAtom* aLocalName) { + const nsStaticAtom* atom; + while ((atom = *aURLs)) { + if (atom == aLocalName) { + return true; + } + ++aURLs; + } + return false; +} + +bool nsTreeSanitizer::MustPrune(int32_t aNamespace, nsAtom* aLocal, + mozilla::dom::Element* aElement) { + if (mIsForSanitizerAPI) { + return MustPruneForSanitizerAPI(aNamespace, aLocal, aElement); + } + + // To avoid attacks where a MathML script becomes something that gets + // serialized in a way that it parses back as an HTML script, let's just + // drop elements with the local name 'script' regardless of namespace. + if (nsGkAtoms::script == aLocal) { + return true; + } + if (aNamespace == kNameSpaceID_XHTML) { + if (nsGkAtoms::title == aLocal && !mFullDocument) { + // emulate the quirks of the old parser + return true; + } + if (mDropForms && + (nsGkAtoms::select == aLocal || nsGkAtoms::button == aLocal || + nsGkAtoms::datalist == aLocal)) { + return true; + } + if (mDropMedia && + (nsGkAtoms::img == aLocal || nsGkAtoms::video == aLocal || + nsGkAtoms::audio == aLocal || nsGkAtoms::source == aLocal)) { + return true; + } + if (nsGkAtoms::meta == aLocal && + (aElement->HasAttr(nsGkAtoms::charset) || + aElement->HasAttr(nsGkAtoms::httpEquiv))) { + // Throw away charset declarations even if they also have microdata + // which they can't validly have. + return true; + } + if (((!mFullDocument && nsGkAtoms::meta == aLocal) || + nsGkAtoms::link == aLocal) && + !(aElement->HasAttr(nsGkAtoms::itemprop) || + aElement->HasAttr(nsGkAtoms::itemscope))) { + // emulate old behavior for non-Microdata <meta> and <link> presumably + // in <head>. <meta> and <link> are whitelisted in order to avoid + // corrupting Microdata when they appear in <body>. Note that + // SanitizeAttributes() will remove the rel attribute from <link> and + // the name attribute from <meta>. + return true; + } + } + if (mAllowStyles) { + return nsGkAtoms::style == aLocal && !(aNamespace == kNameSpaceID_XHTML || + aNamespace == kNameSpaceID_SVG); + } + if (nsGkAtoms::style == aLocal) { + return true; + } + return false; +} + +enum class ElementKind { + Regular, + Custom, + Unknown, +}; + +// https://wicg.github.io/sanitizer-api/#element-kind +static ElementKind GetElementKind(int32_t aNamespace, nsAtom* aLocal, + Element* aElement) { + // XXX(bug 1782926) The spec for this is known to be wrong. + // https://github.com/WICG/sanitizer-api/issues/147 + + // custom, if element’s local name is a valid custom element name, + // XXX shouldn't this happen after unknown. + if (nsContentUtils::IsCustomElementName(aLocal, kNameSpaceID_XHTML)) { + return ElementKind::Custom; + } + + // unknown, if element is not in the [HTML] namespace + // XXX this doesn't really make sense to me + // https://github.com/WICG/sanitizer-api/issues/167 + if (aNamespace != kNameSpaceID_XHTML) { + return ElementKind::Unknown; + } + + // or if element’s local name denotes an unknown element + // — that is, if the element interface the [HTML] specification assigns to it + // would be HTMLUnknownElement, + if (nsCOMPtr<HTMLUnknownElement> el = do_QueryInterface(aElement)) { + return ElementKind::Unknown; + } + + // regular, otherwise. + return ElementKind::Regular; +} + +bool nsTreeSanitizer::MustPruneForSanitizerAPI(int32_t aNamespace, + nsAtom* aLocal, + Element* aElement) { + // This implements everything in + // https://wicg.github.io/sanitizer-api/#sanitize-action-for-an-element that + // is supposed to be dropped. + + // Step 1. Let kind be element’s element kind. + ElementKind kind = GetElementKind(aNamespace, aLocal, aElement); + + switch (kind) { + case ElementKind::Regular: + // Step 2. If kind is regular and element does not match any name in the + // baseline element allow list: Return drop. + if (!sBaselineElementAllowlist->Contains(aLocal)) { + return true; + } + break; + + case ElementKind::Custom: + // Step 3. If kind is custom and if config["allowCustomElements"] does not + // exist or if config["allowCustomElements"] is false: Return drop. + if (!mAllowCustomElements) { + return true; + } + break; + + case ElementKind::Unknown: + // Step 4. If kind is unknown and if config["allowUnknownMarkup"] does not + // exist or it config["allowUnknownMarkup"] is false: Return drop. + if (!mAllowUnknownMarkup) { + return true; + } + break; + } + + // Step 5. If element matches any name in config["dropElements"]: Return drop. + if (mRemoveElements && + MatchesElementName(*mRemoveElements, aNamespace, aLocal)) { + return true; + } + + return false; +} + +/** + * Parses a style sheet and reserializes it with unsafe styles removed. + * + * @param aOriginal the original style sheet source + * @param aSanitized the reserialization without dangerous CSS. + * @param aDocument the document the style sheet belongs to + * @param aBaseURI the base URI to use + * @param aSanitizationKind the kind of style sanitization to use. + */ +static void SanitizeStyleSheet(const nsAString& aOriginal, + nsAString& aSanitized, Document* aDocument, + nsIURI* aBaseURI, + StyleSanitizationKind aSanitizationKind) { + aSanitized.Truncate(); + + NS_ConvertUTF16toUTF8 style(aOriginal); + nsIReferrerInfo* referrer = + aDocument->ReferrerInfoForInternalCSSAndSVGResources(); + auto extraData = + MakeRefPtr<URLExtraData>(aBaseURI, referrer, aDocument->NodePrincipal()); + RefPtr<StyleStylesheetContents> contents = + Servo_StyleSheet_FromUTF8Bytes( + /* loader = */ nullptr, + /* stylesheet = */ nullptr, + /* load_data = */ nullptr, &style, + css::SheetParsingMode::eAuthorSheetFeatures, extraData.get(), + aDocument->GetCompatibilityMode(), + /* reusable_sheets = */ nullptr, + /* use_counters = */ nullptr, StyleAllowImportRules::Yes, + aSanitizationKind, &aSanitized) + .Consume(); +} + +bool nsTreeSanitizer::SanitizeInlineStyle( + Element* aElement, StyleSanitizationKind aSanitizationKind) { + MOZ_ASSERT(aElement); + MOZ_ASSERT(aElement->IsHTMLElement(nsGkAtoms::style) || + aElement->IsSVGElement(nsGkAtoms::style)); + + nsAutoString styleText; + nsContentUtils::GetNodeTextContent(aElement, false, styleText); + + nsAutoString sanitizedStyle; + SanitizeStyleSheet(styleText, sanitizedStyle, aElement->OwnerDoc(), + aElement->GetBaseURI(), StyleSanitizationKind::Standard); + RemoveAllAttributesFromDescendants(aElement); + nsContentUtils::SetNodeTextContent(aElement, sanitizedStyle, true); + + return sanitizedStyle.Length() != styleText.Length(); +} + +void nsTreeSanitizer::RemoveConditionalCSSFromSubtree(nsINode* aRoot) { + AutoTArray<RefPtr<nsINode>, 10> nodesToSanitize; + for (nsINode* node : ShadowIncludingTreeIterator(*aRoot)) { + if (node->IsHTMLElement(nsGkAtoms::style) || + node->IsSVGElement(nsGkAtoms::style)) { + nodesToSanitize.AppendElement(node); + } + } + for (nsINode* node : nodesToSanitize) { + SanitizeInlineStyle(node->AsElement(), + StyleSanitizationKind::NoConditionalRules); + } +} + +template <size_t Len> +static bool UTF16StringStartsWith(const char16_t* aStr, uint32_t aLength, + const char16_t (&aNeedle)[Len]) { + MOZ_ASSERT(aNeedle[Len - 1] == '\0', + "needle should be a UTF-16 encoded string literal"); + + if (aLength < Len - 1) { + return false; + } + for (size_t i = 0; i < Len - 1; i++) { + if (aStr[i] != aNeedle[i]) { + return false; + } + } + return true; +} + +void nsTreeSanitizer::SanitizeAttributes(mozilla::dom::Element* aElement, + AllowedAttributes aAllowed) { + int32_t ac = (int)aElement->GetAttrCount(); + + for (int32_t i = ac - 1; i >= 0; --i) { + const nsAttrName* attrName = aElement->GetAttrNameAt(i); + int32_t attrNs = attrName->NamespaceID(); + RefPtr<nsAtom> attrLocal = attrName->LocalName(); + + if (mIsForSanitizerAPI) { + if (MustDropAttribute(aElement, attrNs, attrLocal) || + MustDropFunkyAttribute(aElement, attrNs, attrLocal)) { + aElement->UnsetAttr(kNameSpaceID_None, attrLocal, false); + if (mLogRemovals) { + LogMessage("Removed unsafe attribute.", aElement->OwnerDoc(), + aElement, attrLocal); + } + + // in case the attribute removal shuffled the attribute order, start + // the loop again. + --ac; + i = ac; // i will be decremented immediately thanks to the for loop + } + continue; + } + + if (kNameSpaceID_None == attrNs) { + if (aAllowed.mStyle && nsGkAtoms::style == attrLocal) { + continue; + } + if (aAllowed.mDangerousSrc && nsGkAtoms::src == attrLocal) { + continue; + } + if (IsURL(aAllowed.mURLs, attrLocal)) { + bool fragmentOnly = aElement->IsSVGElement(nsGkAtoms::use); + if (SanitizeURL(aElement, attrNs, attrLocal, fragmentOnly)) { + // in case the attribute removal shuffled the attribute order, start + // the loop again. + --ac; + i = ac; // i will be decremented immediately thanks to the for loop + continue; + } + // else fall through to see if there's another reason to drop this + // attribute (in particular if the attribute is background="" on an + // HTML element) + } + if (!mDropNonCSSPresentation && + (aAllowed.mNames == sAttributesHTML) && // element is HTML + sPresAttributesHTML->Contains(attrLocal)) { + continue; + } + if (aAllowed.mNames->Contains(attrLocal) && + !((attrLocal == nsGkAtoms::rel && + aElement->IsHTMLElement(nsGkAtoms::link)) || + (!mFullDocument && attrLocal == nsGkAtoms::name && + aElement->IsHTMLElement(nsGkAtoms::meta)))) { + // name="" and rel="" are whitelisted, but treat them as blacklisted + // for <meta name> (fragment case) and <link rel> (all cases) to avoid + // document-wide metadata or styling overrides with non-conforming + // <meta name itemprop> or + // <link rel itemprop> + continue; + } + const char16_t* localStr = attrLocal->GetUTF16String(); + uint32_t localLen = attrLocal->GetLength(); + // Allow underscore to cater to the MCE editor library. + // Allow data-* on SVG and MathML, too, as a forward-compat measure. + // Allow aria-* on all for simplicity. + if (UTF16StringStartsWith(localStr, localLen, u"_") || + UTF16StringStartsWith(localStr, localLen, u"data-") || + UTF16StringStartsWith(localStr, localLen, u"aria-")) { + continue; + } + // else not allowed + } else if (kNameSpaceID_XML == attrNs) { + if (nsGkAtoms::lang == attrLocal || nsGkAtoms::space == attrLocal) { + continue; + } + // else not allowed + } else if (aAllowed.mXLink && kNameSpaceID_XLink == attrNs) { + if (nsGkAtoms::href == attrLocal) { + bool fragmentOnly = aElement->IsSVGElement(nsGkAtoms::use); + if (SanitizeURL(aElement, attrNs, attrLocal, fragmentOnly)) { + // in case the attribute removal shuffled the attribute order, start + // the loop again. + --ac; + i = ac; // i will be decremented immediately thanks to the for loop + } + continue; + } + if (nsGkAtoms::type == attrLocal || nsGkAtoms::title == attrLocal || + nsGkAtoms::show == attrLocal || nsGkAtoms::actuate == attrLocal) { + continue; + } + // else not allowed + } + aElement->UnsetAttr(kNameSpaceID_None, attrLocal, false); + if (mLogRemovals) { + LogMessage("Removed unsafe attribute.", aElement->OwnerDoc(), aElement, + attrLocal); + } + // in case the attribute removal shuffled the attribute order, start the + // loop again. + --ac; + i = ac; // i will be decremented immediately thanks to the for loop + } + + // If we've got HTML audio or video, add the controls attribute, because + // otherwise the content is unplayable with scripts removed. + if (aElement->IsAnyOfHTMLElements(nsGkAtoms::video, nsGkAtoms::audio)) { + aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::controls, u""_ns, false); + } +} + +// https://wicg.github.io/sanitizer-api/#element-matches-an-element-name +bool nsTreeSanitizer::MatchesElementName(ElementNameSet& aNames, + int32_t aNamespace, + nsAtom* aLocalName) { + return aNames.Contains(ElementName(aNamespace, aLocalName)); +} + +bool nsTreeSanitizer::MatchesAttributeName(AttributeNameSet& aNames, + int32_t aNamespace, + nsAtom* aLocalName) { + return aNames.Contains(AttributeName(aNamespace, aLocalName)); +} + +// https://wicg.github.io/sanitizer-api/#sanitize-action-for-an-attribute +bool nsTreeSanitizer::MustDropAttribute(Element* aElement, + int32_t aAttrNamespace, + nsAtom* aAttrLocalName) { + // Step 1. Let kind be attribute’s attribute kind. + // Step 2. If kind is unknown and if config["allowUnknownMarkup"] does not + // exist or it config["allowUnknownMarkup"] is false: Return drop. + // + // TODO: Not clear how to determine if something is an "unknown" attribute. + // https://github.com/WICG/sanitizer-api/issues/147 should probably define + // an explicit list. + + // Step 3. If kind is regular and attribute’s local name does not match any + // name in the baseline attribute allow list: Return drop. + if (!sBaselineAttributeAllowlist->Contains(aAttrLocalName)) { + return true; + } + + // TODO(not specified yet): An element to attributes mapping that is + // considered before the attributes/removeAttributes lists that apply to + // everything. + if (mElements) { + int32_t namespaceID = aElement->NodeInfo()->NamespaceID(); + RefPtr<nsAtom> nameAtom = aElement->NodeInfo()->NameAtom(); + + if (auto entry = mElements->Lookup(ElementName(namespaceID, nameAtom))) { + if (entry->mRemoveAttributes && + MatchesAttributeName(*entry->mRemoveAttributes, aAttrNamespace, + aAttrLocalName)) { + return true; + } + + if (entry->mAttributes) { + if (!MatchesAttributeName(*entry->mAttributes, aAttrNamespace, + aAttrLocalName)) { + return true; + } + // Fall-through to the removeAttributes/attributes lists below. + } + } + } + + // Step 4. If attribute matches any attribute match list in config’s attribute + // drop list: Return drop. + if (mRemoveAttributes && + MatchesAttributeName(*mRemoveAttributes, aAttrNamespace, + aAttrLocalName)) { + return true; + } + + // Step 5. If attribute allow list exists in config: + if (mAttributes) { + // Step 5.1. Then let allow list be |config|["allowAttributes"]. + // Step 6. If attribute does not match any attribute match list in allow + // list: Return drop. + if (!MatchesAttributeName(*mAttributes, aAttrNamespace, aAttrLocalName)) { + return true; + } + } else { + // Step 5.2. Otherwise: Let allow list be the default configuration's + // attribute allow list. + // Step 6. If attribute does not match any attribute + // match list in allow list: Return drop. + if (!sDefaultConfigurationAttributeAllowlist->Contains(aAttrLocalName)) { + return true; + } + } + + // Step 7. Return keep. + return false; +} + +// https://wicg.github.io/sanitizer-api/#handle-funky-elements +bool nsTreeSanitizer::MustDropFunkyAttribute(Element* aElement, + int32_t aAttrNamespace, + nsAtom* aAttrLocalName) { + // Step 1. If element’s element interface is HTMLTemplateElement: + // Note: This step is implemented in the main loop of SanitizeChildren. + + // Step 2. If element’s element interface has a HTMLHyperlinkElementUtils + // mixin, and if element’s protocol property is "javascript:": + // TODO(https://github.com/WICG/sanitizer-api/issues/168) + if (aAttrLocalName == nsGkAtoms::href) { + if (nsCOMPtr<Link> link = do_QueryInterface(aElement)) { + nsCOMPtr<nsIURI> uri = link->GetURI(); + if (uri && uri->SchemeIs("javascript")) { + // Step 2.1. Remove the `href` attribute from element. + return true; + } + } + } + + // Step 3. if element’s element interface is HTMLFormElement, and if element’s + // action attribute is a URL with "javascript:" protocol: + if (auto* form = HTMLFormElement::FromNode(aElement)) { + if (aAttrNamespace == kNameSpaceID_None && + aAttrLocalName == nsGkAtoms::action) { + nsCOMPtr<nsIURI> uri; + form->GetURIAttr(aAttrLocalName, nullptr, getter_AddRefs(uri)); + if (uri && uri->SchemeIs("javascript")) { + // Step 3.1 Remove the `action` attribute from element. + return true; + } + } + } + + // Step 4. if element’s element interface is HTMLInputElement or + // HTMLButtonElement, and if element’s formaction attribute is a [URL] with + // javascript: protocol + if (aElement->IsAnyOfHTMLElements(nsGkAtoms::input, nsGkAtoms::button) && + aAttrNamespace == kNameSpaceID_None && + aAttrLocalName == nsGkAtoms::formaction) { + // XXX nsGenericHTMLFormControlElementWithState::GetFormAction falls back to + // the document URI. + nsGenericHTMLElement* el = nsGenericHTMLElement::FromNode(aElement); + nsCOMPtr<nsIURI> uri; + el->GetURIAttr(aAttrLocalName, nullptr, getter_AddRefs(uri)); + if (uri && uri->SchemeIs("javascript")) { + // Step 4.1 Remove the `formaction` attribute from element. + return true; + } + } + + return false; +} + +bool nsTreeSanitizer::SanitizeURL(mozilla::dom::Element* aElement, + int32_t aNamespace, nsAtom* aLocalName, + bool aFragmentsOnly) { + nsAutoString value; + aElement->GetAttr(aNamespace, aLocalName, value); + + // Get value and remove mandatory quotes + static const char* kWhitespace = "\n\r\t\b"; + const nsAString& v = nsContentUtils::TrimCharsInSet(kWhitespace, value); + // Fragment-only url cannot be harmful. + if (!v.IsEmpty() && v.First() == u'#') { + return false; + } + // if we allow only same-document fragment URLs, stop and remove here + if (aFragmentsOnly) { + aElement->UnsetAttr(aNamespace, aLocalName, false); + if (mLogRemovals) { + LogMessage("Removed unsafe URI from element attribute.", + aElement->OwnerDoc(), aElement, aLocalName); + } + return true; + } + + nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); + uint32_t flags = nsIScriptSecurityManager::DISALLOW_INHERIT_PRINCIPAL; + + nsCOMPtr<nsIURI> attrURI; + nsresult rv = + NS_NewURI(getter_AddRefs(attrURI), v, nullptr, aElement->GetBaseURI()); + if (NS_SUCCEEDED(rv)) { + if (mCidEmbedsOnly && kNameSpaceID_None == aNamespace) { + if (nsGkAtoms::src == aLocalName || nsGkAtoms::background == aLocalName) { + // comm-central uses a hack that makes nsIURIs created with cid: specs + // actually have an about:blank spec. Therefore, nsIURI facilities are + // useless for cid: when comm-central code is participating. + if (!(v.Length() > 4 && (v[0] == 'c' || v[0] == 'C') && + (v[1] == 'i' || v[1] == 'I') && (v[2] == 'd' || v[2] == 'D') && + v[3] == ':')) { + rv = NS_ERROR_FAILURE; + } + } else if (nsGkAtoms::cdgroup_ == aLocalName || + nsGkAtoms::altimg_ == aLocalName || + nsGkAtoms::definitionURL_ == aLocalName) { + // Gecko doesn't fetch these now and shouldn't in the future, but + // in case someone goofs with these in the future, let's drop them. + rv = NS_ERROR_FAILURE; + } else { + rv = secMan->CheckLoadURIWithPrincipal(sNullPrincipal, attrURI, flags, + 0); + } + } else { + rv = secMan->CheckLoadURIWithPrincipal(sNullPrincipal, attrURI, flags, 0); + } + } + if (NS_FAILED(rv)) { + aElement->UnsetAttr(aNamespace, aLocalName, false); + if (mLogRemovals) { + LogMessage("Removed unsafe URI from element attribute.", + aElement->OwnerDoc(), aElement, aLocalName); + } + return true; + } + return false; +} + +void nsTreeSanitizer::Sanitize(DocumentFragment* aFragment) { + // If you want to relax these preconditions, be sure to check the code in + // here that notifies / does not notify or that fires mutation events if + // in tree. + MOZ_ASSERT(!aFragment->IsInUncomposedDoc(), "The fragment is in doc?"); + + mFullDocument = false; + SanitizeChildren(aFragment); +} + +void nsTreeSanitizer::Sanitize(Document* aDocument) { + // If you want to relax these preconditions, be sure to check the code in + // here that notifies / does not notify or that fires mutation events if + // in tree. +#ifdef DEBUG + MOZ_ASSERT(!aDocument->GetContainer(), "The document is in a shell."); + RefPtr<mozilla::dom::Element> root = aDocument->GetRootElement(); + MOZ_ASSERT(root->IsHTMLElement(nsGkAtoms::html), "Not HTML root."); +#endif + + mFullDocument = true; + SanitizeChildren(aDocument); +} + +void nsTreeSanitizer::SanitizeChildren(nsINode* aRoot) { + nsIContent* node = aRoot->GetFirstChild(); + while (node) { + if (node->IsElement()) { + mozilla::dom::Element* elt = node->AsElement(); + mozilla::dom::NodeInfo* nodeInfo = node->NodeInfo(); + nsAtom* localName = nodeInfo->NameAtom(); + int32_t ns = nodeInfo->NamespaceID(); + + if (MustPrune(ns, localName, elt)) { + if (mLogRemovals) { + LogMessage("Removing unsafe node.", elt->OwnerDoc(), elt); + } + RemoveAllAttributes(elt); + nsIContent* descendant = node; + while ((descendant = descendant->GetNextNode(node))) { + if (descendant->IsElement()) { + RemoveAllAttributes(descendant->AsElement()); + } + } + nsIContent* next = node->GetNextNonChildNode(aRoot); + node->RemoveFromParent(); + node = next; + continue; + } + if (auto* templateEl = HTMLTemplateElement::FromNode(elt)) { + // traverse into the DocFragment content attribute of template elements + bool wasFullDocument = mFullDocument; + mFullDocument = false; + RefPtr<DocumentFragment> frag = templateEl->Content(); + SanitizeChildren(frag); + mFullDocument = wasFullDocument; + } + if (!mIsForSanitizerAPI && nsGkAtoms::style == localName) { + // If styles aren't allowed, style elements got pruned above. Even + // if styles are allowed, non-HTML, non-SVG style elements got pruned + // above. + NS_ASSERTION(ns == kNameSpaceID_XHTML || ns == kNameSpaceID_SVG, + "Should have only HTML or SVG here!"); + if (SanitizeInlineStyle(elt, StyleSanitizationKind::Standard) && + mLogRemovals) { + LogMessage("Removed some rules and/or properties from stylesheet.", + aRoot->OwnerDoc()); + } + + AllowedAttributes allowed; + allowed.mStyle = mAllowStyles; + if (ns == kNameSpaceID_XHTML) { + allowed.mNames = sAttributesHTML; + allowed.mURLs = kURLAttributesHTML; + } else { + allowed.mNames = sAttributesSVG; + allowed.mURLs = kURLAttributesSVG; + allowed.mXLink = true; + } + SanitizeAttributes(elt, allowed); + node = node->GetNextNonChildNode(aRoot); + continue; + } + if (MustFlatten(ns, localName)) { + if (mLogRemovals) { + LogMessage("Flattening unsafe node (descendants are preserved).", + elt->OwnerDoc(), elt); + } + RemoveAllAttributes(elt); + nsCOMPtr<nsIContent> next = node->GetNextNode(aRoot); + nsCOMPtr<nsIContent> parent = node->GetParent(); + nsCOMPtr<nsIContent> child; // Must keep the child alive during move + ErrorResult rv; + while ((child = node->GetFirstChild())) { + nsCOMPtr<nsINode> refNode = node; + parent->InsertBefore(*child, refNode, rv); + if (rv.Failed()) { + break; + } + } + node->RemoveFromParent(); + node = next; + continue; + } + NS_ASSERTION(ns == kNameSpaceID_XHTML || ns == kNameSpaceID_SVG || + ns == kNameSpaceID_MathML, + "Should have only HTML, MathML or SVG here!"); + AllowedAttributes allowed; + if (ns == kNameSpaceID_XHTML) { + allowed.mNames = sAttributesHTML; + allowed.mURLs = kURLAttributesHTML; + allowed.mStyle = mAllowStyles; + allowed.mDangerousSrc = nsGkAtoms::img == localName && !mCidEmbedsOnly; + SanitizeAttributes(elt, allowed); + } else if (ns == kNameSpaceID_SVG) { + allowed.mNames = sAttributesSVG; + allowed.mURLs = kURLAttributesSVG; + allowed.mXLink = true; + allowed.mStyle = mAllowStyles; + SanitizeAttributes(elt, allowed); + } else { + allowed.mNames = sAttributesMathML; + allowed.mURLs = kURLAttributesMathML; + allowed.mXLink = true; + SanitizeAttributes(elt, allowed); + } + node = node->GetNextNode(aRoot); + continue; + } + NS_ASSERTION(!node->GetFirstChild(), "How come non-element node had kids?"); + nsIContent* next = node->GetNextNonChildNode(aRoot); + if (!mAllowComments && node->IsComment()) { + node->RemoveFromParent(); + } + node = next; + } +} + +void nsTreeSanitizer::RemoveAllAttributes(Element* aElement) { + const nsAttrName* attrName; + while ((attrName = aElement->GetAttrNameAt(0))) { + int32_t attrNs = attrName->NamespaceID(); + RefPtr<nsAtom> attrLocal = attrName->LocalName(); + aElement->UnsetAttr(attrNs, attrLocal, false); + } +} + +void nsTreeSanitizer::RemoveAllAttributesFromDescendants( + mozilla::dom::Element* aElement) { + nsIContent* node = aElement->GetFirstChild(); + while (node) { + if (node->IsElement()) { + mozilla::dom::Element* elt = node->AsElement(); + RemoveAllAttributes(elt); + } + node = node->GetNextNode(aElement); + } +} + +void nsTreeSanitizer::LogMessage(const char* aMessage, Document* aDoc, + Element* aElement, nsAtom* aAttr) { + if (mLogRemovals) { + nsAutoString msg; + msg.Assign(NS_ConvertASCIItoUTF16(aMessage)); + if (aElement) { + msg.Append(u" Element: "_ns + aElement->LocalName() + u"."_ns); + } + if (aAttr) { + msg.Append(u" Attribute: "_ns + nsDependentAtomString(aAttr) + u"."_ns); + } + + if (mInnerWindowID) { + nsContentUtils::ReportToConsoleByWindowID( + msg, nsIScriptError::warningFlag, "DOM"_ns, mInnerWindowID); + } else { + nsContentUtils::ReportToConsoleNonLocalized( + msg, nsIScriptError::warningFlag, "DOM"_ns, aDoc); + } + } +} + +void nsTreeSanitizer::InitializeStatics() { + MOZ_ASSERT(!sElementsHTML, "Initializing a second time."); + + sElementsHTML = new AtomsTable(ArrayLength(kElementsHTML)); + for (uint32_t i = 0; kElementsHTML[i]; i++) { + sElementsHTML->Insert(kElementsHTML[i]); + } + + sAttributesHTML = new AtomsTable(ArrayLength(kAttributesHTML)); + for (uint32_t i = 0; kAttributesHTML[i]; i++) { + sAttributesHTML->Insert(kAttributesHTML[i]); + } + + sPresAttributesHTML = new AtomsTable(ArrayLength(kPresAttributesHTML)); + for (uint32_t i = 0; kPresAttributesHTML[i]; i++) { + sPresAttributesHTML->Insert(kPresAttributesHTML[i]); + } + + sElementsSVG = new AtomsTable(ArrayLength(kElementsSVG)); + for (uint32_t i = 0; kElementsSVG[i]; i++) { + sElementsSVG->Insert(kElementsSVG[i]); + } + + sAttributesSVG = new AtomsTable(ArrayLength(kAttributesSVG)); + for (uint32_t i = 0; kAttributesSVG[i]; i++) { + sAttributesSVG->Insert(kAttributesSVG[i]); + } + + sElementsMathML = new AtomsTable(ArrayLength(kElementsMathML)); + for (uint32_t i = 0; kElementsMathML[i]; i++) { + sElementsMathML->Insert(kElementsMathML[i]); + } + + sAttributesMathML = new AtomsTable(ArrayLength(kAttributesMathML)); + for (uint32_t i = 0; kAttributesMathML[i]; i++) { + sAttributesMathML->Insert(kAttributesMathML[i]); + } + + sBaselineAttributeAllowlist = + new AtomsTable(ArrayLength(kBaselineAttributeAllowlist)); + for (const auto* atom : kBaselineAttributeAllowlist) { + sBaselineAttributeAllowlist->Insert(atom); + } + + sBaselineElementAllowlist = + new AtomsTable(ArrayLength(kBaselineElementAllowlist)); + for (const auto* atom : kBaselineElementAllowlist) { + sBaselineElementAllowlist->Insert(atom); + } + + sDefaultConfigurationAttributeAllowlist = + new AtomsTable(ArrayLength(kDefaultConfigurationAttributeAllowlist)); + for (const auto* atom : kDefaultConfigurationAttributeAllowlist) { + sDefaultConfigurationAttributeAllowlist->Insert(atom); + } + + sDefaultConfigurationElementAllowlist = + new AtomsTable(ArrayLength(kDefaultConfigurationElementAllowlist)); + for (const auto* atom : kDefaultConfigurationElementAllowlist) { + sDefaultConfigurationElementAllowlist->Insert(atom); + } + + nsCOMPtr<nsIPrincipal> principal = + NullPrincipal::CreateWithoutOriginAttributes(); + principal.forget(&sNullPrincipal); +} + +void nsTreeSanitizer::ReleaseStatics() { + delete sElementsHTML; + sElementsHTML = nullptr; + + delete sAttributesHTML; + sAttributesHTML = nullptr; + + delete sPresAttributesHTML; + sPresAttributesHTML = nullptr; + + delete sElementsSVG; + sElementsSVG = nullptr; + + delete sAttributesSVG; + sAttributesSVG = nullptr; + + delete sElementsMathML; + sElementsMathML = nullptr; + + delete sAttributesMathML; + sAttributesMathML = nullptr; + + delete sBaselineAttributeAllowlist; + sBaselineAttributeAllowlist = nullptr; + + delete sBaselineElementAllowlist; + sBaselineElementAllowlist = nullptr; + + delete sDefaultConfigurationAttributeAllowlist; + sDefaultConfigurationAttributeAllowlist = nullptr; + + delete sDefaultConfigurationElementAllowlist; + sDefaultConfigurationElementAllowlist = nullptr; + + NS_IF_RELEASE(sNullPrincipal); +} + +static int32_t ConvertNamespaceString(const nsAString& aNamespace, + bool aForAttribute, + mozilla::ErrorResult& aRv) { + if (aNamespace.IsVoid()) { + // NOTE: Currently this ?should? never match any elements, only + // attributes. + return kNameSpaceID_None; + } + + int32_t namespaceID = nsNameSpaceManager::GetInstance()->GetNameSpaceID( + aNamespace, /* aInChromeDoc */ false); + if (namespaceID == kNameSpaceID_XHTML || namespaceID == kNameSpaceID_MathML || + namespaceID == kNameSpaceID_SVG) { + return namespaceID; + } + if (aForAttribute && (namespaceID == kNameSpaceID_XMLNS || + namespaceID == kNameSpaceID_XLink)) { + return namespaceID; + } + + aRv.ThrowTypeError("Invalid namespace: \""_ns + + NS_ConvertUTF16toUTF8(aNamespace) + "\"."_ns); + return kNameSpaceID_Unknown; +} + +nsTreeSanitizer::ElementNameSet nsTreeSanitizer::ConvertElements( + const nsTArray<OwningStringOrSanitizerElementNamespace>& aElements, + mozilla::ErrorResult& aRv) { + ElementNameSet set(aElements.Length()); + + for (const auto& entry : aElements) { + if (entry.IsString()) { + RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(entry.GetAsString()); + // The default namespace for elements is HTML. + ElementName elemName(kNameSpaceID_XHTML, std::move(nameAtom)); + set.Insert(elemName); + } else { + const auto& elemNamespace = entry.GetAsSanitizerElementNamespace(); + + int32_t namespaceID = + ConvertNamespaceString(elemNamespace.mNamespace, false, aRv); + if (aRv.Failed()) { + break; + } + RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(elemNamespace.mName); + ElementName elemName(namespaceID, std::move(nameAtom)); + set.Insert(elemName); + } + } + + return set; +} + +nsTreeSanitizer::ElementsToAttributesMap +nsTreeSanitizer::ConvertElementsWithAttributes( + const nsTArray<OwningStringOrSanitizerElementNamespaceWithAttributes>& + aElements, + mozilla::ErrorResult& aRv) { + ElementsToAttributesMap map; + + for (const auto& entry : aElements) { + if (entry.IsString()) { + RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(entry.GetAsString()); + // The default namespace for elements is HTML. + ElementName elemName(kNameSpaceID_XHTML, std::move(nameAtom)); + // No explicit list of attributes to allow/remove. + map.InsertOrUpdate(elemName, ElementWithAttributes{}); + } else { + const auto& elemNamespace = + entry.GetAsSanitizerElementNamespaceWithAttributes(); + + ElementWithAttributes elemWithAttributes; + + if (elemNamespace.mAttributes.WasPassed()) { + elemWithAttributes.mAttributes.emplace( + ConvertAttributes(elemNamespace.mAttributes.Value(), aRv)); + if (aRv.Failed()) { + break; + } + } + + if (elemNamespace.mRemoveAttributes.WasPassed()) { + elemWithAttributes.mRemoveAttributes.emplace( + ConvertAttributes(elemNamespace.mRemoveAttributes.Value(), aRv)); + if (aRv.Failed()) { + break; + } + } + + int32_t namespaceID = + ConvertNamespaceString(elemNamespace.mNamespace, false, aRv); + if (aRv.Failed()) { + break; + } + + RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(elemNamespace.mName); + ElementName elemName(namespaceID, std::move(nameAtom)); + + map.InsertOrUpdate(elemName, std::move(elemWithAttributes)); + } + } + + return map; +} + +nsTreeSanitizer::AttributeNameSet nsTreeSanitizer::ConvertAttributes( + const nsTArray<OwningStringOrSanitizerAttributeNamespace>& aAttributes, + ErrorResult& aRv) { + AttributeNameSet set(aAttributes.Length()); + for (const auto& entry : aAttributes) { + if (entry.IsString()) { + RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(entry.GetAsString()); + // The default namespace for attributes is the "null" namespace. + AttributeName attrName(kNameSpaceID_None, std::move(nameAtom)); + set.Insert(attrName); + } else { + const auto& attrNamespace = entry.GetAsSanitizerAttributeNamespace(); + int32_t namespaceID = + ConvertNamespaceString(attrNamespace.mNamespace, true, aRv); + if (aRv.Failed()) { + break; + } + RefPtr<nsAtom> attrAtom = NS_AtomizeMainThread(attrNamespace.mName); + AttributeName attrName(namespaceID, std::move(attrAtom)); + set.Insert(attrName); + } + } + return set; +} + +void nsTreeSanitizer::WithWebSanitizerOptions( + nsIGlobalObject* aGlobal, const mozilla::dom::SanitizerConfig& aOptions, + ErrorResult& aRv) { + if (StaticPrefs::dom_security_sanitizer_logging()) { + mLogRemovals = true; + if (nsPIDOMWindowInner* win = aGlobal->GetAsInnerWindow()) { + mInnerWindowID = win->WindowID(); + } + } + + mIsForSanitizerAPI = true; + + if (aOptions.mComments.WasPassed()) { + mAllowComments = aOptions.mComments.Value(); + } + if (aOptions.mCustomElements.WasPassed()) { + mAllowCustomElements = aOptions.mCustomElements.Value(); + } + if (aOptions.mUnknownMarkup.WasPassed()) { + mAllowUnknownMarkup = aOptions.mUnknownMarkup.Value(); + } + + if (aOptions.mElements.WasPassed()) { + mElements.emplace( + ConvertElementsWithAttributes(aOptions.mElements.Value(), aRv)); + if (aRv.Failed()) { + return; + } + } + + if (aOptions.mRemoveElements.WasPassed()) { + mRemoveElements.emplace( + ConvertElements(aOptions.mRemoveElements.Value(), aRv)); + if (aRv.Failed()) { + return; + } + } + + if (aOptions.mReplaceWithChildrenElements.WasPassed()) { + mReplaceWithChildrenElements.emplace( + ConvertElements(aOptions.mReplaceWithChildrenElements.Value(), aRv)); + if (aRv.Failed()) { + return; + } + } + + if (aOptions.mAttributes.WasPassed()) { + mAttributes.emplace(ConvertAttributes(aOptions.mAttributes.Value(), aRv)); + if (aRv.Failed()) { + return; + } + } + + if (aOptions.mRemoveAttributes.WasPassed()) { + mRemoveAttributes.emplace( + ConvertAttributes(aOptions.mRemoveAttributes.Value(), aRv)); + } +} |