/* -*- 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/StyleSheetInlines.h" #include "mozilla/UniquePtr.h" #include "mozilla/css/Rule.h" #include "mozilla/dom/SanitizerBinding.h" #include "mozilla/dom/CSSRuleList.h" #include "mozilla/dom/DocumentFragment.h" #include "mozilla/dom/HTMLTemplateElement.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::mfenced_, // mfenced 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 }; 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; 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), mOnlyConditionalCSS(aFlags & nsIParserUtils::SanitizerRemoveOnlyConditionalCSS) { if (mCidEmbedsOnly) { // Sanitizing styles for external references is not supported. mAllowStyles = false; } mAllowedElements = nullptr; mBlockedElements = nullptr; if (!sElementsHTML) { // Initialize lazily to avoid having to initialize at all if the user // doesn't paste HTML or load feeds. InitializeStatics(); } /* Ensure SanitizerRemoveOnlyConditionalCSS isn't combined with any * flags, except SanitizerLogRemovals. */ MOZ_ASSERT(!mOnlyConditionalCSS || 0 == (aFlags & ~(nsIParserUtils::SanitizerRemoveOnlyConditionalCSS | nsIParserUtils::SanitizerLogRemovals))); } bool nsTreeSanitizer::MustFlatten(int32_t aNamespace, nsAtom* aLocal) { if (aNamespace == kNameSpaceID_XHTML) { if (mIsCustomized) { // TODO(freddy): Make it work for other namespaces. // See https://github.com/WICG/sanitizer-api/issues/72 return ((mAllowedElements && !mAllowedElements->Contains(aLocal)) || ((mBlockedElements && mBlockedElements->Contains(aLocal)))); } 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::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) { // 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; } void nsTreeSanitizer::SanitizeStyleSheet(const nsAString& aOriginal, nsAString& aSanitized, Document* aDocument, nsIURI* aBaseURI) { aSanitized.Truncate(); NS_ConvertUTF16toUTF8 style(aOriginal); RefPtr referrer = ReferrerInfo::CreateForInternalCSSResources(aDocument); auto extraData = MakeRefPtr(aBaseURI, referrer, aDocument->NodePrincipal()); auto sanitizationKind = mOnlyConditionalCSS ? StyleSanitizationKind::NoConditionalRules : StyleSanitizationKind::Standard; 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, sanitizationKind, &aSanitized) .Consume(); if (mLogRemovals && aSanitized.Length() != aOriginal.Length()) { LogMessage("Removed some rules and/or properties from stylesheet.", aDocument); } } 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 (mIsCustomized) { bool shouldRemove = true; RefPtr elemName = aElement->NodeInfo()->NameAtom(); // check allow list if (mAllowedAttributes) { auto allowedElements = mAllowedAttributes->Lookup(attrLocal); if (allowedElements) { if (allowedElements.Data()->Contains(elemName) || allowedElements.Data()->Contains(nsGkAtoms::_asterisk)) { shouldRemove = false; } } } // checking drop list last // i.e., if listd as both allowed and dropped, it will still be dropped if (mDroppedAttributes) { auto dropElements = mDroppedAttributes->Lookup(attrLocal); if (dropElements) { if (dropElements.Data()->Contains(elemName) || dropElements.Data()->Contains(nsGkAtoms::_asterisk)) { shouldRemove = true; } } } if (shouldRemove) { aElement->UnsetAttr(kNameSpaceID_None, attrLocal, false); // 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)) { if (SanitizeURL(aElement, attrNs, 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; } // 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) { if (SanitizeURL(aElement, attrNs, 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 (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); } } bool nsTreeSanitizer::SanitizeURL(mozilla::dom::Element* aElement, int32_t aNamespace, nsAtom* aLocalName) { 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; } 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 (!mOnlyConditionalCSS && 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 (nsGkAtoms::style == localName) { // If !mOnlyConditionalCSS check the following condition: // 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) || mOnlyConditionalCSS, "Should have only HTML or SVG here!"); nsAutoString styleText; nsContentUtils::GetNodeTextContent(node, false, styleText); nsAutoString sanitizedStyle; SanitizeStyleSheet(styleText, sanitizedStyle, aRoot->OwnerDoc(), node->GetBaseURI()); RemoveAllAttributesFromDescendants(elt); nsContentUtils::SetNodeTextContent(node, sanitizedStyle, true); if (!mOnlyConditionalCSS) { AllowedAttributes allowed; allowed.mStyle = mAllowStyles; if (ns == kNameSpaceID_XHTML) { allowed.mNames = sAttributesHTML; allowed.mURLs = kURLAttributesHTML; SanitizeAttributes(elt, allowed); } else { allowed.mNames = sAttributesSVG; allowed.mURLs = kURLAttributesSVG; allowed.mXLink = true; SanitizeAttributes(elt, allowed); } } node = node->GetNextNonChildNode(aRoot); continue; } if (!mOnlyConditionalCSS && 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; } if (!mOnlyConditionalCSS) { 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 (!mOnlyConditionalCSS && (!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); } 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]); } 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; NS_IF_RELEASE(sNullPrincipal); } void nsTreeSanitizer::WithWebSanitizerOptions( const mozilla::dom::SanitizerConfig& aOptions) { if (!aOptions.IsAnyMemberPresent()) { return; } if (aOptions.mAllowComments.WasPassed()) { mAllowComments = aOptions.mAllowComments.Value(); } if (aOptions.mAllowElements.WasPassed()) { mIsCustomized = true; const Sequence& allowedElements = aOptions.mAllowElements.Value(); mAllowedElements = MakeUnique(allowedElements.Length()); for (const nsString& elem : allowedElements) { nsAutoString lowercaseElem; nsContentUtils::ASCIIToLower(elem, lowercaseElem); RefPtr elAsAtom = NS_Atomize(lowercaseElem); mAllowedElements->Insert(elAsAtom); } } else { mAllowedElements = nullptr; } if (aOptions.mBlockElements.WasPassed()) { mIsCustomized = true; const Sequence& blockedElements = aOptions.mBlockElements.Value(); mBlockedElements = MakeUnique(blockedElements.Length()); for (const nsString& elem : blockedElements) { nsAutoString lowercaseElem; nsContentUtils::ASCIIToLower(elem, lowercaseElem); RefPtr elAsAtom = NS_Atomize(lowercaseElem); mBlockedElements->Insert(elAsAtom); } } else { mBlockedElements = nullptr; } if (aOptions.mAllowAttributes.WasPassed()) { mIsCustomized = true; const Record>& allowedAttributes = aOptions.mAllowAttributes.Value(); mAllowedAttributes = MakeUnique< nsTHashMap, mozilla::UniquePtr>>(); nsAutoString name; for (const auto& entries : allowedAttributes.Entries()) { UniquePtr elems = MakeUnique(allowedAttributes.Entries().Length()); for (const auto& elem : entries.mValue) { nsAutoString lowercaseElem; nsContentUtils::ASCIIToLower(elem, lowercaseElem); RefPtr elAsAtom = NS_Atomize(lowercaseElem); elems->Insert(elAsAtom); } nsAutoString attrName; nsContentUtils::ASCIIToLower(entries.mKey, attrName); RefPtr attrAtom = NS_Atomize(attrName); mAllowedAttributes->InsertOrUpdate(attrAtom, std::move(elems)); } } else { mAllowedAttributes = nullptr; } if (aOptions.mDropAttributes.WasPassed()) { mIsCustomized = true; const Record>& droppedAttributes = aOptions.mDropAttributes.Value(); mDroppedAttributes = MakeUnique< nsTHashMap, mozilla::UniquePtr>>(); nsAutoString name; for (const auto& entries : droppedAttributes.Entries()) { UniquePtr elems = MakeUnique(droppedAttributes.Entries().Length()); for (const auto& elem : entries.mValue) { nsAutoString lowercaseElem; nsContentUtils::ASCIIToLower(elem, lowercaseElem); RefPtr elAsAtom = NS_Atomize(lowercaseElem); elems->Insert(elAsAtom); } nsAutoString attrName; nsContentUtils::ASCIIToLower(entries.mKey, attrName); RefPtr attrAtom = NS_Atomize(attrName); mDroppedAttributes->InsertOrUpdate(attrAtom, std::move(elems)); } } else { mDroppedAttributes = nullptr; } // TODO(freddy) Add handling of other keys in SanitizerConfig }