@@ -808,225 +808,83 @@ function edac_svg_markup_to_data_uri( $svg_markup ): string {
808808
809809/**
810810 * Allowed tags/attributes for edac_sanitize_scanned_html() - wp_kses_allowed_html( 'post' )
811- * plus the non-scripting SVG vocabulary actually likely to appear in a
812- * flagged icon/logo/decorative graphic: structure, shapes, text, and basic
813- * gradients/patterns/masks. Deliberately excludes <script>, <foreignObject>,
814- * <image>, <a> (SVG's own href-based link element - ordinary post-content
815- * links are still allowed via the 'post' base list), SMIL animation
816- * elements, and filter primitives - none of which this plugin's real-world
817- * content needs, and never lists any on* attribute for anything.
811+ * plus the minimal SVG vocabulary a flagged icon/logo/decorative graphic
812+ * actually uses: container, grouping, basic shapes, gradients, text, and the
813+ * accessible-name elements (<title>/<desc>). Deliberately excludes <script>,
814+ * <foreignObject>, <image>, <a> (SVG's own href-based link element -
815+ * ordinary post-content links are still allowed via the 'post' base list),
816+ * SMIL animation, filter primitives, and rarely-used structural extras
817+ * (<pattern>, <mask>, <marker>, <switch>, <textPath>) - none of which this
818+ * plugin's real-world content needs. Never lists any on* attribute for
819+ * anything.
820+ *
821+ * Every allowed SVG element shares one attribute set: wp_kses() only needs
822+ * attribute names allow-listed, not semantically scoped per tag, and a
823+ * geometry/paint attribute on a tag that ignores it is harmless. The only
824+ * exception is href/xlink:href, which stays scoped to <use> (the icon-sprite
825+ * pattern) and is protocol-validated - see edac_sanitize_scanned_html().
818826 *
819827 * @since x.x.x
820828 *
821829 * @return array<string, array<string, bool>>
822830 */
823831function edac_scanned_html_allowed_tags (): array {
824- $ allowed = wp_kses_allowed_html ( 'post ' );
825-
826- // Shared by (almost) every SVG element - core presentation attributes
827- // plus the accessibility attributes this plugin most cares about.
828- $ common = [
829- 'id ' => true ,
830- 'class ' => true ,
831- 'style ' => true ,
832- 'transform ' => true ,
833- 'role ' => true ,
834- 'aria-hidden ' => true ,
835- 'aria-label ' => true ,
836- 'aria-labelledby ' => true ,
837- 'aria-describedby ' => true ,
838- 'focusable ' => true ,
839- 'tabindex ' => true ,
840- 'lang ' => true ,
841- 'xml:lang ' => true ,
842- 'xml:space ' => true ,
843- ];
844-
845- // Paint/stroke/fill presentation attributes - valid on most shape and
846- // container elements per the SVG spec; wp_kses doesn't need them to be
847- // semantically scoped per tag, only explicitly allow-listed.
848- $ paint = [
849- 'fill ' => true ,
850- 'fill-rule ' => true ,
851- 'fill-opacity ' => true ,
852- 'stroke ' => true ,
853- 'stroke-width ' => true ,
854- 'stroke-linecap ' => true ,
855- 'stroke-linejoin ' => true ,
856- 'stroke-dasharray ' => true ,
857- 'stroke-dashoffset ' => true ,
858- 'stroke-opacity ' => true ,
859- 'stroke-miterlimit ' => true ,
860- 'opacity ' => true ,
861- 'color ' => true ,
862- 'clip-path ' => true ,
863- 'clip-rule ' => true ,
864- 'mask ' => true ,
865- 'filter ' => true ,
866- 'marker-start ' => true ,
867- 'marker-mid ' => true ,
868- 'marker-end ' => true ,
869- 'vector-effect ' => true ,
870- 'shape-rendering ' => true ,
871- ];
872-
873- $ common_paint = $ common + $ paint ;
832+ $ identity = [ 'id ' , 'class ' , 'style ' , 'transform ' , 'role ' , 'focusable ' ];
833+ $ aria = [ 'aria-hidden ' , 'aria-label ' , 'aria-labelledby ' , 'aria-describedby ' ];
834+ $ root = [ 'xmlns ' , 'xmlns:xlink ' , 'version ' , 'viewbox ' , 'preserveaspectratio ' ];
835+ $ geometry = [ 'x ' , 'y ' , 'width ' , 'height ' , 'cx ' , 'cy ' , 'r ' , 'rx ' , 'ry ' , 'x1 ' , 'y1 ' , 'x2 ' , 'y2 ' , 'fx ' , 'fy ' , 'd ' , 'points ' , 'dx ' , 'dy ' ];
836+ $ paint = [ 'fill ' , 'fill-rule ' , 'fill-opacity ' , 'stroke ' , 'stroke-width ' , 'stroke-linecap ' , 'stroke-linejoin ' , 'stroke-dasharray ' , 'stroke-dashoffset ' , 'stroke-opacity ' , 'stroke-miterlimit ' , 'opacity ' , 'clip-path ' , 'clip-rule ' ];
837+ $ gradient = [ 'gradientunits ' , 'gradienttransform ' , 'spreadmethod ' , 'offset ' , 'stop-color ' , 'stop-opacity ' ];
838+ $ text = [ 'text-anchor ' , 'font-family ' , 'font-size ' , 'font-weight ' , 'font-style ' ];
839+
840+ $ svg_attributes = array_fill_keys (
841+ array_merge ( $ identity , $ aria , $ root , $ geometry , $ paint , $ gradient , $ text ),
842+ true
843+ );
874844
875- // href/xlink:href only ever added to elements whose sole use is a local
876- // same-document fragment reference (#id) - edac_sanitize_scanned_html()
877- // registers xlink:href with wp_kses_uri_attributes() for the duration
878- // of the call, so both get the same bad-protocol/URI validation core
879- // already applies to the plain 'href' attribute.
880- $ href = [
881- 'href ' => true ,
882- 'xlink:href ' => true ,
845+ $ svg_elements = [
846+ 'svg ' ,
847+ 'g ' ,
848+ 'defs ' ,
849+ 'symbol ' ,
850+ 'use ' ,
851+ 'path ' ,
852+ 'rect ' ,
853+ 'circle ' ,
854+ 'ellipse ' ,
855+ 'line ' ,
856+ 'polyline ' ,
857+ 'polygon ' ,
858+ 'lineargradient ' ,
859+ 'radialgradient ' ,
860+ 'stop ' ,
861+ 'clippath ' ,
862+ 'text ' ,
863+ 'tspan ' ,
864+ 'title ' ,
865+ 'desc ' ,
883866 ];
884867
885- $ svg_tags = [
886- 'svg ' => $ common + [
887- 'xmlns ' => true ,
888- 'xmlns:xlink ' => true ,
889- 'version ' => true ,
890- 'viewbox ' => true ,
891- 'width ' => true ,
892- 'height ' => true ,
893- 'preserveaspectratio ' => true ,
894- ],
895- 'g ' => $ common_paint ,
896- 'defs ' => $ common ,
897- 'symbol ' => $ common + [
898- 'viewbox ' => true ,
899- 'preserveaspectratio ' => true ,
900- ],
901- 'switch ' => $ common ,
902- 'use ' => $ common_paint + $ href + [
903- 'x ' => true ,
904- 'y ' => true ,
905- 'width ' => true ,
906- 'height ' => true ,
907- ],
908- 'path ' => $ common_paint + [ 'd ' => true ],
909- 'rect ' => $ common_paint + [
910- 'x ' => true ,
911- 'y ' => true ,
912- 'width ' => true ,
913- 'height ' => true ,
914- 'rx ' => true ,
915- 'ry ' => true ,
916- ],
917- 'circle ' => $ common_paint + [
918- 'cx ' => true ,
919- 'cy ' => true ,
920- 'r ' => true ,
921- ],
922- 'ellipse ' => $ common_paint + [
923- 'cx ' => true ,
924- 'cy ' => true ,
925- 'rx ' => true ,
926- 'ry ' => true ,
927- ],
928- 'line ' => $ common_paint + [
929- 'x1 ' => true ,
930- 'y1 ' => true ,
931- 'x2 ' => true ,
932- 'y2 ' => true ,
933- ],
934- 'polyline ' => $ common_paint + [ 'points ' => true ],
935- 'polygon ' => $ common_paint + [ 'points ' => true ],
936- 'text ' => $ common_paint + [
937- 'x ' => true ,
938- 'y ' => true ,
939- 'dx ' => true ,
940- 'dy ' => true ,
941- 'text-anchor ' => true ,
942- 'font-family ' => true ,
943- 'font-size ' => true ,
944- 'font-weight ' => true ,
945- 'font-style ' => true ,
946- ],
947- 'tspan ' => $ common_paint + [
948- 'x ' => true ,
949- 'y ' => true ,
950- 'dx ' => true ,
951- 'dy ' => true ,
952- ],
953- 'textpath ' => $ common_paint + $ href + [
954- 'startoffset ' => true ,
955- 'method ' => true ,
956- 'spacing ' => true ,
957- 'side ' => true ,
958- ],
959- 'lineargradient ' => $ common + $ href + [
960- 'x1 ' => true ,
961- 'y1 ' => true ,
962- 'x2 ' => true ,
963- 'y2 ' => true ,
964- 'gradientunits ' => true ,
965- 'gradienttransform ' => true ,
966- 'spreadmethod ' => true ,
967- ],
968- 'radialgradient ' => $ common + $ href + [
969- 'cx ' => true ,
970- 'cy ' => true ,
971- 'r ' => true ,
972- 'fx ' => true ,
973- 'fy ' => true ,
974- 'fr ' => true ,
975- 'gradientunits ' => true ,
976- 'gradienttransform ' => true ,
977- 'spreadmethod ' => true ,
978- ],
979- 'stop ' => $ common + [
980- 'offset ' => true ,
981- 'stop-color ' => true ,
982- 'stop-opacity ' => true ,
983- ],
984- 'pattern ' => $ common + $ href + [
985- 'x ' => true ,
986- 'y ' => true ,
987- 'width ' => true ,
988- 'height ' => true ,
989- 'patternunits ' => true ,
990- 'patterncontentunits ' => true ,
991- 'patterntransform ' => true ,
992- 'viewbox ' => true ,
993- ],
994- 'clippath ' => $ common + [ 'clippathunits ' => true ],
995- 'mask ' => $ common + [
996- 'x ' => true ,
997- 'y ' => true ,
998- 'width ' => true ,
999- 'height ' => true ,
1000- 'maskunits ' => true ,
1001- 'maskcontentunits ' => true ,
1002- ],
1003- 'marker ' => $ common + [
1004- 'markerwidth ' => true ,
1005- 'markerheight ' => true ,
1006- 'markerunits ' => true ,
1007- 'refx ' => true ,
1008- 'refy ' => true ,
1009- 'orient ' => true ,
1010- 'viewbox ' => true ,
1011- 'preserveaspectratio ' => true ,
1012- ],
1013- 'title ' => $ common ,
1014- 'desc ' => $ common ,
1015- ];
868+ $ svg = array_fill_keys ( $ svg_elements , $ svg_attributes );
1016869
1017- foreach ( $ svg_tags as $ tag => $ attrs ) {
1018- $ allowed [ $ tag ] = $ attrs ;
1019- }
870+ // Local same-document fragment reference (#id) for the icon-sprite
871+ // pattern. edac_sanitize_scanned_html() registers xlink:href with
872+ // wp_kses_uri_attributes() for the duration of the call, so both get
873+ // the same bad-protocol/URI validation core already applies to the
874+ // plain 'href' attribute.
875+ $ svg ['use ' ]['href ' ] = true ;
876+ $ svg ['use ' ]['xlink:href ' ] = true ;
1020877
1021- return $ allowed ;
878+ return array_merge ( wp_kses_allowed_html ( ' post ' ), $ svg ) ;
1022879}
1023880
1024881/**
1025882 * SVG attribute names are case-sensitive per spec (e.g. viewBox,
1026883 * gradientTransform), but wp_kses() - built for case-insensitive HTML -
1027884 * lowercases every attribute name it outputs. Left alone, that silently
1028885 * breaks otherwise-safe SVGs (a lowercased viewbox is simply ignored by
1029- * browsers). Keyed by the lowercased name wp_kses() produces.
886+ * browsers). Keyed by the lowercased name wp_kses() produces. Must stay in
887+ * sync with the camelCase attributes in edac_scanned_html_allowed_tags().
1030888 *
1031889 * @since x.x.x
1032890 *
@@ -1039,18 +897,6 @@ function edac_svg_case_sensitive_attributes(): array {
1039897 'gradientunits ' => 'gradientUnits ' ,
1040898 'gradienttransform ' => 'gradientTransform ' ,
1041899 'spreadmethod ' => 'spreadMethod ' ,
1042- 'patternunits ' => 'patternUnits ' ,
1043- 'patterncontentunits ' => 'patternContentUnits ' ,
1044- 'patterntransform ' => 'patternTransform ' ,
1045- 'clippathunits ' => 'clipPathUnits ' ,
1046- 'maskunits ' => 'maskUnits ' ,
1047- 'maskcontentunits ' => 'maskContentUnits ' ,
1048- 'markerwidth ' => 'markerWidth ' ,
1049- 'markerheight ' => 'markerHeight ' ,
1050- 'markerunits ' => 'markerUnits ' ,
1051- 'refx ' => 'refX ' ,
1052- 'refy ' => 'refY ' ,
1053- 'startoffset ' => 'startOffset ' ,
1054900 ];
1055901}
1056902
0 commit comments