/* -*- 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/BindingStyleRule.h" #include "mozilla/DeclarationBlock.h" #include "mozilla/StaticPrefs_dom.h" #include "mozilla/StyleSheetInlines.h" #include "mozilla/UniquePtr.h" #include "mozilla/css/Rule.h" #include "mozilla/dom/CSSRuleList.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 "nsNetUtil.h" #include "nsComponentManagerUtils.h" #include "nsContentUtils.h" #include "nsIParserUtils.h" #include "mozilla/dom/Document.h" #include "nsQueryObject.h" #include 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 (mBlockElements && MatchesElementName(*mBlockElements, 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 (mAllowElements) { // Step 9. If element does not match any name in allow list: // Return block. if (!MatchesElementName(*mAllowElements, 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(kNameSpaceID_None, nsGkAtoms::charset) || aElement->HasAttr(kNameSpaceID_None, 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(kNameSpaceID_None, nsGkAtoms::itemprop) || aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::itemscope))) { // emulate old behavior for non-Microdata and presumably // in . and are whitelisted in order to avoid // corrupting Microdata when they appear in . Note that // SanitizeAttributes() will remove the rel attribute from and // the name attribute from . 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 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 (mDropElements && MatchesElementName(*mDropElements, 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); RefPtr referrer = ReferrerInfo::CreateForInternalCSSResources(aDocument); auto extraData = MakeRefPtr(aBaseURI, referrer, aDocument->NodePrincipal()); RefPtr contents = Servo_StyleSheet_FromUTF8Bytes( /* loader = */ nullptr, /* stylesheet = */ nullptr, /* load_data = */ nullptr, &style, css::SheetParsingMode::eAuthorSheetFeatures, extraData.get(), /* line_number_offset = */ 0, 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) { for (nsINode* node : ShadowIncludingTreeIterator(*aRoot)) { if (!node->IsHTMLElement(nsGkAtoms::style) && !node->IsSVGElement(nsGkAtoms::style)) { continue; } SanitizeInlineStyle(node->AsElement(), StyleSanitizationKind::NoConditionalRules); } } template 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 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 (fragment case) and (all cases) to avoid // document-wide metadata or styling overrides with non-conforming // or // 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)); } // https://wicg.github.io/sanitizer-api/#attribute-match-list bool nsTreeSanitizer::MatchesAttributeMatchList( ElementToAttributeSetTable& aMatchList, Element& aElement, int32_t aAttrNamespace, nsAtom* aAttrLocalName) { // Step 1. If attribute’s local name does not match the attribute match list // list’s key and if the key is not "*": Return false. ElementNameSet* names; if (auto lookup = aMatchList.Lookup(aAttrLocalName)) { names = lookup->get(); } else if (auto lookup = aMatchList.Lookup(nsGkAtoms::_asterisk)) { names = lookup->get(); } else { return false; } // Step 2. Let element be the attribute’s Element. // Step 3. Let element name be element’s local name. // Step 4. If element is a in either the SVG or MathML namespaces (i.e., it’s // a foreign element), then prefix element name with the appropriate namespace // designator plus a whitespace character. int32_t namespaceID = aElement.NodeInfo()->NamespaceID(); RefPtr nameAtom = aElement.NodeInfo()->NameAtom(); ElementName elemName(namespaceID, nameAtom); // Step 5. If list’s value does not contain element name and value is not // ["*"]: Return false. if (!names->Contains(elemName) && !names->Contains(ElementName(kNameSpaceID_XHTML, nsGkAtoms::_asterisk))) { return false; } // Step 6. Return true. return true; } // 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; } // Step 4. If attribute matches any attribute match list in config’s attribute // drop list: Return drop. if (mDroppedAttributes && MatchesAttributeMatchList(*mDroppedAttributes, *aElement, aAttrNamespace, aAttrLocalName)) { return true; } // Step 5. If attribute allow list exists in config: if (mAllowedAttributes) { // 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 (!MatchesAttributeMatchList(*mAllowedAttributes, *aElement, 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 = do_QueryInterface(aElement)) { nsCOMPtr 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 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 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 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 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 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 next = node->GetNextNode(aRoot); nsCOMPtr parent = node->GetParent(); nsCOMPtr child; // Must keep the child alive during move ErrorResult rv; while ((child = node->GetFirstChild())) { nsCOMPtr 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 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 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); } UniquePtr nsTreeSanitizer::ConvertElementNames( const Sequence& aNames) { auto names = MakeUnique(aNames.Length()); // https://wicg.github.io/sanitizer-api/#normalize-element-name for (const nsString& name : aNames) { // Step 1. Let tokens be the result of strictly splitting name on the // delimiter ":" (U+003A). int32_t index = name.FindChar(':'); // Step 2. If tokens’ size is 1, then return tokens[0]. if (index == kNotFound) { RefPtr nameAtom = NS_AtomizeMainThread(name); ElementName elemName(kNameSpaceID_XHTML, std::move(nameAtom)); names->Insert(elemName); continue; } // Step 3. If tokens’ size is 2 and tokens[0] is either "svg" or "math", // then: if (name.FindChar(':', index + 1) == kNotFound) { auto prefix = Substring(name, 0, index); // Step 3.1. Adjust tokens[1] as described in the "any other start tag" // branch of the rules for parsing tokens in foreign content subchapter in // the HTML parsing spec. Step 3.2 Return the concatenation of the list // «|tokens|[0],":" (U+003A),|tokens|[1]». // TODO RefPtr nameAtom = NS_AtomizeMainThread(Substring(name, index + 1)); if (prefix.EqualsLiteral("svg")) { ElementName elemName(kNameSpaceID_SVG, std::move(nameAtom)); names->Insert(elemName); } else if (prefix.EqualsLiteral("math")) { ElementName elemName(kNameSpaceID_MathML, std::move(nameAtom)); names->Insert(elemName); } } // Step 4. Return null. // Nothing is inserted and name is skipped. } return names; } void nsTreeSanitizer::WithWebSanitizerOptions( nsIGlobalObject* aGlobal, const mozilla::dom::SanitizerConfig& aOptions) { if (StaticPrefs::dom_security_sanitizer_logging()) { mLogRemovals = true; if (nsPIDOMWindowInner* win = aGlobal->AsInnerWindow()) { mInnerWindowID = win->WindowID(); } } mIsForSanitizerAPI = true; if (aOptions.mAllowComments.WasPassed()) { mAllowComments = aOptions.mAllowComments.Value(); } if (aOptions.mAllowCustomElements.WasPassed()) { mAllowCustomElements = aOptions.mAllowCustomElements.Value(); } if (aOptions.mAllowUnknownMarkup.WasPassed()) { mAllowUnknownMarkup = aOptions.mAllowUnknownMarkup.Value(); } if (aOptions.mAllowElements.WasPassed()) { mAllowElements = ConvertElementNames(aOptions.mAllowElements.Value()); } if (aOptions.mBlockElements.WasPassed()) { mBlockElements = ConvertElementNames(aOptions.mBlockElements.Value()); } if (aOptions.mDropElements.WasPassed()) { mDropElements = ConvertElementNames(aOptions.mDropElements.Value()); } if (aOptions.mAllowAttributes.WasPassed()) { const Record>& allowedAttributes = aOptions.mAllowAttributes.Value(); mAllowedAttributes = MakeUnique(); for (const auto& entry : allowedAttributes.Entries()) { RefPtr attrAtom = NS_AtomizeMainThread(entry.mKey); UniquePtr elements = ConvertElementNames(entry.mValue); mAllowedAttributes->InsertOrUpdate(attrAtom, std::move(elements)); } } if (aOptions.mDropAttributes.WasPassed()) { const Record>& droppedAttributes = aOptions.mDropAttributes.Value(); mDroppedAttributes = MakeUnique(); for (const auto& entry : droppedAttributes.Entries()) { RefPtr attrAtom = NS_AtomizeMainThread(entry.mKey); UniquePtr elements = ConvertElementNames(entry.mValue); mDroppedAttributes->InsertOrUpdate(attrAtom, std::move(elements)); } } }