PluginProbe
Post Grid Gutenberg Blocks – PostX / 5.1.0
Post Grid Gutenberg Blocks – PostX v5.1.0
5.1.1 5.1.0 5.0.41 5.0.39 5.0.38 5.0.37 5.0.36 5.0.35 5.0.34 5.0.33 5.0.32 5.0.31 5.0.30 5.0.29 5.0.28 5.0.27 5.0.26 5.0.25 5.0.23 5.0.24 5.0.22 5.0.21 5.0.20 5.0.19 5.0.18 All 240 releases
ultimate-post / addons / custom_font / Svg_Font_Sanitizer.php

Svg_Font_Sanitizer.php in Post Grid Gutenberg Blocks – PostX 5.1.0, at addons/custom_font/Svg_Font_Sanitizer.php

220 lines 5.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Sanitizer for uploaded SVG font files.
4 *
5 * @package ULTP
6 */
7
8 namespace ULTP;
9
10 defined( 'ABSPATH' ) || exit;
11
12 /**
13 * Keeps only the parts of an SVG file that describe a font.
14 *
15 * Allowlist based: every element and attribute that is not listed is removed,
16 * so scripts, event handlers, links, styles and foreign markup cannot survive.
17 */
18 class Svg_Font_Sanitizer {
19
20 const SVG_NS = 'http://www.w3.org/2000/svg';
21
22 /**
23 * Elements kept, by local name. Anything else is removed with its children.
24 *
25 * @var array
26 */
27 private static $allowed_elements = array(
28 'svg',
29 'defs',
30 'metadata',
31 'title',
32 'desc',
33 'font',
34 'font-face',
35 'glyph',
36 'missing-glyph',
37 'hkern',
38 'vkern',
39 'path',
40 );
41
42 /**
43 * Attributes kept, by local name. Namespaced attributes are always removed.
44 *
45 * @var array
46 */
47 private static $allowed_attributes = array(
48 // Root and shared.
49 'id',
50 'version',
51 'baseprofile',
52 'width',
53 'height',
54 'viewbox',
55 'x',
56 'y',
57 // <font>.
58 'horiz-origin-x',
59 'horiz-origin-y',
60 'horiz-adv-x',
61 'vert-origin-x',
62 'vert-origin-y',
63 'vert-adv-y',
64 // <font-face>.
65 'font-family',
66 'font-style',
67 'font-variant',
68 'font-weight',
69 'font-stretch',
70 'font-size',
71 'unicode-range',
72 'units-per-em',
73 'panose-1',
74 'stemv',
75 'stemh',
76 'slope',
77 'cap-height',
78 'x-height',
79 'accent-height',
80 'ascent',
81 'descent',
82 'widths',
83 'bbox',
84 'ideographic',
85 'alphabetic',
86 'mathematical',
87 'hanging',
88 'v-ideographic',
89 'v-alphabetic',
90 'v-mathematical',
91 'v-hanging',
92 'underline-position',
93 'underline-thickness',
94 'strikethrough-position',
95 'strikethrough-thickness',
96 'overline-position',
97 'overline-thickness',
98 // <glyph>, <missing-glyph>, <path>.
99 'unicode',
100 'glyph-name',
101 'd',
102 'orientation',
103 'arabic-form',
104 'lang',
105 'transform',
106 // <hkern>, <vkern>.
107 'u1',
108 'u2',
109 'g1',
110 'g2',
111 'k',
112 );
113
114 /**
115 * Sanitize an SVG font file in place.
116 *
117 * @param string $path Absolute path to the file.
118 * @return true|\WP_Error True when the file was cleaned and rewritten.
119 */
120 public static function sanitize_file( $path ) {
121 $svg = file_get_contents( $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Local upload in the temp directory.
122 if ( false === $svg || '' === trim( $svg ) ) {
123 return self::error();
124 }
125
126 $clean = self::sanitize( $svg );
127 if ( is_wp_error( $clean ) ) {
128 return $clean;
129 }
130
131 if ( false === file_put_contents( $path, $clean ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- Local upload in the temp directory.
132 return self::error();
133 }
134 return true;
135 }
136
137 /**
138 * Sanitize SVG markup.
139 *
140 * @param string $svg SVG markup.
141 * @return string|\WP_Error Clean markup, or an error when the file is not a usable SVG font.
142 */
143 public static function sanitize( $svg ) {
144 // Entity declarations are never needed by a font and are the basis of XXE and entity-expansion attacks.
145 if ( false !== stripos( $svg, '<!ENTITY' ) ) {
146 return self::error();
147 }
148
149 if ( ! class_exists( '\DOMDocument' ) ) {
150 return self::error();
151 }
152
153 $use_errors = libxml_use_internal_errors( true );
154 $dom = new \DOMDocument();
155 $loaded = $dom->loadXML( $svg, LIBXML_NONET );
156 libxml_clear_errors();
157 libxml_use_internal_errors( $use_errors );
158
159 if ( ! $loaded || ! $dom->documentElement || 'svg' !== $dom->documentElement->localName ) {
160 return self::error();
161 }
162
163 self::clean_node( $dom->documentElement );
164
165 // After cleaning, a file without a font in the SVG namespace is not an SVG font.
166 if ( 0 === $dom->getElementsByTagNameNS( self::SVG_NS, 'font' )->length ) {
167 return self::error();
168 }
169
170 $clean = $dom->saveXML( $dom->documentElement );
171 if ( false === $clean ) {
172 return self::error();
173 }
174 return '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . $clean;
175 }
176
177 /**
178 * Remove disallowed attributes from an element and disallowed nodes below it.
179 *
180 * @param \DOMElement $element Element to clean.
181 */
182 private static function clean_node( \DOMElement $element ) {
183 $remove = array();
184 foreach ( $element->attributes as $attribute ) {
185 if ( $attribute->namespaceURI || ! in_array( strtolower( $attribute->localName ), self::$allowed_attributes, true ) ) {
186 $remove[] = $attribute;
187 }
188 }
189 foreach ( $remove as $attribute ) {
190 $element->removeAttributeNode( $attribute );
191 }
192
193 $children = array();
194 foreach ( $element->childNodes as $child ) {
195 $children[] = $child;
196 }
197 foreach ( $children as $child ) {
198 if ( XML_ELEMENT_NODE === $child->nodeType ) {
199 if ( self::SVG_NS === $child->namespaceURI && in_array( $child->localName, self::$allowed_elements, true ) ) {
200 self::clean_node( $child );
201 } else {
202 $element->removeChild( $child );
203 }
204 } elseif ( XML_TEXT_NODE !== $child->nodeType ) {
205 // Comments, CDATA, processing instructions and entity references.
206 $element->removeChild( $child );
207 }
208 }
209 }
210
211 /**
212 * The one error returned for any file that cannot be used.
213 *
214 * @return \WP_Error
215 */
216 private static function error() {
217 return new \WP_Error( 'ultp_invalid_svg_font', __( 'This SVG file is not a valid SVG font, so it was not uploaded. PostX accepts SVG files only when they contain a font.', 'ultimate-post' ) );
218 }
219 }
220