Diff: STRATO-apps/wordpress_03/app/wp-content/plugins/elementor/core/utils/svg/svg-sanitizer.php

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
1 -
1 + <?php
2 + namespace Elementor\Core\Utils\Svg;
3 +
4 + use Elementor\Utils;
5 +
6 + if ( ! defined( 'ABSPATH' ) ) {
7 + exit; // Exit if accessed directly.
8 + }
9 +
10 + /**
11 + * Elementor SVG Sanitizer.
12 + *
13 + * A class that is responsible for sanitizing SVG files.
14 + *
15 + * @since 3.16.0
16 + */
17 + class Svg_Sanitizer {
18 +
19 + /**
20 + * @var \DOMDocument
21 + */
22 + private $svg_dom = null;
23 +
24 + /**
25 + * Sanitize File
26 + *
27 + * @since 3.16.0
28 + * @access public
29 + *
30 + * @param $filename
31 + * @return bool
32 + */
33 + public function sanitize_file( $filename ) {
34 + $original_content = Utils::file_get_contents( $filename );
35 + $is_encoded = $this->is_encoded( $original_content );
36 +
37 + if ( $is_encoded ) {
38 + $decoded = $this->decode_svg( $original_content );
39 + if ( false === $decoded ) {
40 + return false;
41 + }
42 + $original_content = $decoded;
43 + }
44 +
45 + $valid_svg = $this->sanitize( $original_content );
46 +
47 + if ( false === $valid_svg ) {
48 + return false;
49 + }
50 +
51 + // If we were gzipped, we need to re-zip
52 + if ( $is_encoded ) {
53 + $valid_svg = $this->encode_svg( $valid_svg );
54 + }
55 + file_put_contents( $filename, $valid_svg );
56 +
57 + return true;
58 + }
59 +
60 + /**
61 + * Sanitize
62 + *
63 + * @since 3.16.0
64 + * @access public
65 + *
66 + * @param $content
67 + * @return bool|string
68 + */
69 + public function sanitize( $content ) {
70 + // Strip php tags
71 + $content = $this->strip_comments( $content );
72 + $content = $this->strip_php_tags( $content );
73 + $content = $this->strip_line_breaks( $content );
74 +
75 + // Find the start and end tags so we can cut out miscellaneous garbage.
76 + $start = strpos( $content, '<svg' );
77 + $end = strrpos( $content, '</svg>' );
78 + if ( false === $start || false === $end ) {
79 + return false;
80 + }
81 +
82 + $content = substr( $content, $start, ( $end - $start + 6 ) );
83 +
84 + // If the server's PHP version is 8 or up, make sure to Disable the ability to load external entities
85 + $php_version_under_eight = version_compare( PHP_VERSION, '8.0.0', '<' );
86 + if ( $php_version_under_eight ) {
87 + $libxml_disable_entity_loader = libxml_disable_entity_loader( true ); // phpcs:ignore Generic.PHP.DeprecatedFunctions.Deprecated
88 + }
89 + // Suppress the errors
90 + $libxml_use_internal_errors = libxml_use_internal_errors( true );
91 +
92 + // Create DomDocument instance
93 + $this->svg_dom = new \DOMDocument();
94 + $this->svg_dom->formatOutput = false;
95 + $this->svg_dom->preserveWhiteSpace = false;
96 + $this->svg_dom->strictErrorChecking = false;
97 +
98 + $open_svg = $this->svg_dom->loadXML( $content );
99 + if ( ! $open_svg ) {
100 + return false;
101 + }
102 +
103 + $this->strip_doctype();
104 + $this->sanitize_elements();
105 +
106 + // Export sanitized svg to string
107 + // Using documentElement to strip out <?xml version="1.0" encoding="UTF-8"...
108 + $sanitized = $this->svg_dom->saveXML( $this->svg_dom->documentElement, LIBXML_NOEMPTYTAG );
109 +
110 + // Restore defaults
111 + if ( $php_version_under_eight ) {
112 + libxml_disable_entity_loader( $libxml_disable_entity_loader ); // phpcs:ignore Generic.PHP.DeprecatedFunctions.Deprecated
113 + }
114 + libxml_use_internal_errors( $libxml_use_internal_errors );
115 +
116 + return $sanitized;
117 + }
118 +
119 + /**
120 + * Is Encoded
121 + *
122 + * Check if the contents of the SVG file are gzipped
123 + *
124 + * @see http://www.gzip.org/zlib/rfc-gzip.html#member-format
125 + *
126 + * @since 3.16.0
127 + * @access private
128 + *
129 + * @param $contents
130 + *
131 + * @return bool
132 + */
133 + private function is_encoded( $contents ) {
134 + $needle = "\x1f\x8b\x08";
135 + if ( function_exists( 'mb_strpos' ) ) {
136 + return 0 === mb_strpos( $contents, $needle );
137 + } else {
138 + return 0 === strpos( $contents, $needle );
139 + }
140 + }
141 +
142 + /**
143 + * Encode SVG
144 + *
145 + * @since 3.16.0
146 + * @access private
147 + *
148 + * @param $content
149 + * @return string
150 + */
151 + private function encode_svg( $content ) {
152 + return gzencode( $content );
153 + }
154 +
155 + /**
156 + * Decode SVG
157 + *
158 + * @since 3.16.0
159 + * @access private
160 + *
161 + * @param $content
162 + *
163 + * @return string
164 + */
165 + private function decode_svg( $content ) {
166 + return gzdecode( $content );
167 + }
168 +
169 + /**
170 + * Is Allowed Tag
171 + *
172 + * @since 3.16.0
173 + * @access private
174 + *
175 + * @param $element
176 + * @return bool
177 + */
178 + private function is_allowed_tag( $element ) {
179 + static $allowed_tags = false;
180 + if ( false === $allowed_tags ) {
181 + $allowed_tags = $this->get_allowed_elements();
182 + }
183 +
184 + $tag_name = $element->tagName; // phpcs:ignore -- php DomDocument
185 +
186 + if ( ! in_array( strtolower( $tag_name ), $allowed_tags ) ) {
187 + $this->remove_element( $element );
188 + return false;
189 + }
190 +
191 + return true;
192 + }
193 +
194 + /**
195 + * Remove Element
196 + *
197 + * Removes the passed element from its DomDocument tree
198 + *
199 + * @since 3.16.0
200 + * @access private
201 + *
202 + * @param $element
203 + */
204 + private function remove_element( $element ) {
205 + $element->parentNode->removeChild( $element ); // phpcs:ignore -- php DomDocument
206 + }
207 +
208 + /**
209 + * Is It An Attribute
210 + *
211 + * @since 3.16.0
212 + * @access private
213 + *
214 + * @param $name
215 + * @param $check
216 + * @return bool
217 + */
218 + private function is_a_attribute( $name, $check ) {
219 + return 0 === strpos( $name, $check . '-' );
220 + }
221 +
222 + /**
223 + * Is Remote Value
224 + *
225 + * @since 3.16.0
226 + * @access private
227 + *
228 + * @param $value
229 + * @return string
230 + */
231 + private function is_remote_value( $value ) {
232 + $value = trim( preg_replace( '/[^ -~]/xu', '', $value ) );
233 + $wrapped_in_url = preg_match( '~^url\(\s*[\'"]\s*(.*)\s*[\'"]\s*\)$~xi', $value, $match );
234 + if ( ! $wrapped_in_url ) {
235 + return false;
236 + }
237 +
238 + $value = trim( $match[1], '\'"' );
239 + return preg_match( '~^((https?|ftp|file):)?//~xi', $value );
240 + }
241 +
242 + /**
243 + * Has JS Value
244 + *
245 + * @since 3.16.0
246 + * @access private
247 + *
248 + * @param $value
249 + * @return false|int
250 + */
251 + private function has_js_value( $value ) {
252 + return preg_match( '/base64|data|(?:java)?script|alert\(|window\.|document/i', $value );
253 + }
254 +
255 + /**
256 + * Get Allowed Attributes
257 + *
258 + * Returns an array of allowed tag attributes in SVG files.
259 + *
260 + * @since 3.16.0
261 + * @access private
262 + *
263 + * @return array
264 + */
265 + private function get_allowed_attributes() {
266 + $allowed_attributes = [
267 + 'accent-height',
268 + 'accumulate',
269 + 'additivive',
270 + 'alignment-baseline',
271 + 'aria-hidden',
272 + 'aria-controls',
273 + 'aria-describedby',
274 + 'aria-description',
275 + 'aria-expanded',
276 + 'aria-haspopup',
277 + 'aria-label',
278 + 'aria-labelledby',
279 + 'aria-roledescription',
280 + 'ascent',
281 + 'attributename',
282 + 'attributetype',
283 + 'azimuth',
284 + 'basefrequency',
285 + 'baseline-shift',
286 + 'begin',
287 + 'bias',
288 + 'by',
289 + 'class',
290 + 'clip',
291 + 'clip-path',
292 + 'clip-rule',
293 + 'clippathunits',
294 + 'color',
295 + 'color-interpolation',
296 + 'color-interpolation-filters',
297 + 'color-profile',
298 + 'color-rendering',
299 + 'cx',
300 + 'cy',
301 + 'd',
302 + 'dx',
303 + 'dy',
304 + 'diffuseconstant',
305 + 'direction',
306 + 'display',
307 + 'divisor',
308 + 'dominant-baseline',
309 + 'dur',
310 + 'edgemode',
311 + 'elevation',
312 + 'end',
313 + 'fill',
314 + 'fill-opacity',
315 + 'fill-rule',
316 + 'filter',
317 + 'filterres',
318 + 'filterunits',
319 + 'flood-color',
320 + 'flood-opacity',
321 + 'font-family',
322 + 'font-size',
323 + 'font-size-adjust',
324 + 'font-stretch',
325 + 'font-style',
326 + 'font-variant',
327 + 'font-weight',
328 + 'fx',
329 + 'fy',
330 + 'g1',
331 + 'g2',
332 + 'glyph-name',
333 + 'glyphref',
334 + 'gradienttransform',
335 + 'gradientunits',
336 + 'height',
337 + 'href',
338 + 'id',
339 + 'image-rendering',
340 + 'in',
341 + 'in2',
342 + 'k',
343 + 'k1',
344 + 'k2',
345 + 'k3',
346 + 'k4',
347 + 'kerning',
348 + 'keypoints',
349 + 'keysplines',
350 + 'keytimes',
351 + 'lang',
352 + 'lengthadjust',
353 + 'letter-spacing',
354 + 'kernelmatrix',
355 + 'kernelunitlength',
356 + 'lighting-color',
357 + 'local',
358 + 'marker-end',
359 + 'marker-mid',
360 + 'marker-start',
361 + 'markerheight',
362 + 'markerunits',
363 + 'markerwidth',
364 + 'mask',
365 + 'maskcontentunits',
366 + 'maskunits',
367 + 'max',
368 + 'media',
369 + 'method',
370 + 'mode',
371 + 'min',
372 + 'name',
373 + 'numoctaves',
374 + 'offset',
375 + 'opacity',
376 + 'operator',
377 + 'order',
378 + 'orient',
379 + 'orientation',
380 + 'origin',
381 + 'overflow',
382 + 'paint-order',
383 + 'path',
384 + 'pathlength',
385 + 'patterncontentunits',
386 + 'patterntransform',
387 + 'patternunits',
388 + 'points',
389 + 'preservealpha',
390 + 'preserveaspectratio',
391 + 'primitiveunits',
392 + 'r',
393 + 'rx',
394 + 'ry',
395 + 'radius',
396 + 'refx',
397 + 'refy',
398 + 'repeatcount',
399 + 'repeatdur',
400 + 'requiredfeatures',
401 + 'restart',
402 + 'result',
403 + 'role',
404 + 'rotate',
405 + 'scale',
406 + 'seed',
407 + 'shape-rendering',
408 + 'spacing',
409 + 'specularconstant',
410 + 'specularexponent',
411 + 'spreadmethod',
412 + 'startoffset',
413 + 'stddeviation',
414 + 'stitchtiles',
415 + 'stop-color',
416 + 'stop-opacity',
417 + 'stroke',
418 + 'stroke-dasharray',
419 + 'stroke-dashoffset',
420 + 'stroke-linecap',
421 + 'stroke-linejoin',
422 + 'stroke-miterlimit',
423 + 'stroke-opacity',
424 + 'stroke-width',
425 + 'style',
426 + 'surfacescale',
427 + 'systemlanguage',
428 + 'tabindex',
429 + 'targetx',
430 + 'targety',
431 + 'transform',
432 + 'transform-origin',
433 + 'text-anchor',
434 + 'text-decoration',
435 + 'text-rendering',
436 + 'textlength',
437 + 'type',
438 + 'u1',
439 + 'u2',
440 + 'underline-position',
441 + 'underline-thickness',
442 + 'unicode',
443 + 'unicode-bidi',
444 + 'values',
445 + 'vector-effect',
446 + 'vert-adv-y',
447 + 'vert-origin-x',
448 + 'vert-origin-y',
449 + 'viewbox',
450 + 'visibility',
451 + 'width',
452 + 'word-spacing',
453 + 'wrap',
454 + 'writing-mode',
455 + 'x',
456 + 'x1',
457 + 'x2',
458 + 'xchannelselector',
459 + 'xlink:href',
460 + 'xlink:title',
461 + 'xmlns',
462 + 'xmlns:se',
463 + 'xmlns:xlink',
464 + 'xml:lang',
465 + 'xml:space',
466 + 'y',
467 + 'y1',
468 + 'y2',
469 + 'ychannelselector',
470 + 'z',
471 + 'zoomandpan',
472 + ];
473 +
474 + /**
475 + * Allowed attributes in SVG file.
476 + *
477 + * Filters the list of allowed attributes in SVG files.
478 + *
479 + * Since SVG files can run JS code that may inject malicious code, all attributes
480 + * are removed except the allowed attributes.
481 + *
482 + * This hook can be used to manage allowed SVG attributes. To either add new
483 + * attributes or delete existing attributes. To strengthen or weaken site security.
484 + *
485 + * @param array $allowed_attributes A list of allowed attributes.
486 + */
487 + $allowed_attributes = apply_filters( 'elementor/files/svg/allowed_attributes', $allowed_attributes );
488 +
489 + return $allowed_attributes;
490 + }
491 +
492 + /**
493 + * Get Allowed Elements
494 + *
495 + * Returns an array of allowed element tags to be in SVG files.
496 + *
497 + * @since 3.16.0
498 + * @access private
499 + *
500 + * @return array
501 + */
502 + private function get_allowed_elements() {
503 + $allowed_elements = [
504 + 'a',
505 + 'animate',
506 + 'animateMotion',
507 + 'animateTransform',
508 + 'circle',
509 + 'clippath',
510 + 'defs',
511 + 'desc',
512 + 'ellipse',
513 + 'feBlend',
514 + 'feColorMatrix',
515 + 'feComponentTransfer',
516 + 'feComposite',
517 + 'feConvolveMatrix',
518 + 'feDiffuseLighting',
519 + 'feDisplacementMap',
520 + 'feDistantLight',
521 + 'feDropShadow',
522 + 'feFlood',
523 + 'feFuncA',
524 + 'feFuncB',
525 + 'feFuncG',
526 + 'feFuncR',
527 + 'feGaussianBlur',
528 + 'feImage',
529 + 'feMerge',
530 + 'feMergeNode',
531 + 'feMorphology',
532 + 'feOffset',
533 + 'fePointLight',
534 + 'feSpecularLighting',
535 + 'feSpotLight',
536 + 'feTile',
537 + 'feTurbulence',
538 + 'filter',
539 + 'foreignobject',
540 + 'g',
541 + 'image',
542 + 'line',
543 + 'lineargradient',
544 + 'marker',
545 + 'mask',
546 + 'metadata',
547 + 'mpath',
548 + 'path',
549 + 'pattern',
550 + 'polygon',
551 + 'polyline',
552 + 'radialgradient',
553 + 'rect',
554 + 'set',
555 + 'stop',
556 + 'style',
557 + 'svg',
558 + 'switch',
559 + 'symbol',
560 + 'text',
561 + 'textpath',
562 + 'title',
563 + 'tspan',
564 + 'use',
565 + 'view',
566 + ];
567 +
568 + /**
569 + * Allowed elements in SVG file.
570 + *
571 + * Filters the list of allowed elements in SVG files.
572 + *
573 + * Since SVG files can run JS code that may inject malicious code, all elements
574 + * are removed except the allowed elements.
575 + *
576 + * This hook can be used to manage SVG elements. To either add new elements or
577 + * delete existing elements. To strengthen or weaken site security.
578 + *
579 + * @param array $allowed_elements A list of allowed elements.
580 + */
581 + $allowed_elements = apply_filters( 'elementor/files/svg/allowed_elements', $allowed_elements );
582 +
583 + return $allowed_elements;
584 + }
585 +
586 + /**
587 + * Validate Allowed Attributes
588 + *
589 + * @since 3.16.0
590 + * @access private
591 + *
592 + * @param \DOMElement $element
593 + */
594 + private function validate_allowed_attributes( $element ) {
595 + static $allowed_attributes = false;
596 + if ( false === $allowed_attributes ) {
597 + $allowed_attributes = $this->get_allowed_attributes();
598 + }
599 +
600 + for ( $index = $element->attributes->length - 1; $index >= 0; $index-- ) {
601 + // get attribute name
602 + $attr_name = $element->attributes->item( $index )->name;
603 + $attr_name_lowercase = strtolower( $attr_name );
604 + // Remove attribute if not in whitelist
605 + if ( ! in_array( $attr_name_lowercase, $allowed_attributes ) && ! $this->is_a_attribute( $attr_name_lowercase, 'aria' ) && ! $this->is_a_attribute( $attr_name_lowercase, 'data' ) ) {
606 + $element->removeAttribute( $attr_name );
607 + continue;
608 + }
609 +
610 + $attr_value = $element->attributes->item( $index )->value;
611 +
612 + // Remove attribute if it has a remote reference or js or data-URI/base64
613 + if ( ! empty( $attr_value ) && ( $this->is_remote_value( $attr_value ) || $this->has_js_value( $attr_value ) ) ) {
614 + $element->removeAttribute( $attr_name );
615 + continue;
616 + }
617 + }
618 + }
619 +
620 + /**
621 + * Strip xlinks
622 + *
623 + * @since 3.16.0
624 + * @access private
625 + *
626 + * @param \DOMElement $element
627 + */
628 + private function strip_xlinks( $element ) {
629 + $xlinks = $element->getAttributeNS( 'http://www.w3.org/1999/xlink', 'href' );
630 +
631 + if ( ! $xlinks ) {
632 + return;
633 + }
634 +
635 + if ( ! $this->is_safe_href( $xlinks ) ) {
636 + $element->removeAttributeNS( 'http://www.w3.org/1999/xlink', 'href' );
637 + }
638 + }
639 +
640 + /**
641 + * @see https://github.com/darylldoyle/svg-sanitizer/blob/2321a914e/src/Sanitizer.php#L454
642 + */
643 + private function is_safe_href( $value ) {
644 + // Allow empty values.
645 + if ( empty( $value ) ) {
646 + return true;
647 + }
648 +
649 + // Allow fragment identifiers.
650 + if ( '#' === substr( $value, 0, 1 ) ) {
651 + return true;
652 + }
653 +
654 + // Allow relative URIs.
655 + if ( '/' === substr( $value, 0, 1 ) ) {
656 + return true;
657 + }
658 +
659 + // Allow HTTPS domains.
660 + if ( 'https://' === substr( $value, 0, 8 ) ) {
661 + return true;
662 + }
663 +
664 + // Allow HTTP domains.
665 + if ( 'http://' === substr( $value, 0, 7 ) ) {
666 + return true;
667 + }
668 +
669 + // Allow known data URIs.
670 + if ( in_array( substr( $value, 0, 14 ), [
671 + 'data:image/png', // PNG
672 + 'data:image/gif', // GIF
673 + 'data:image/jpg', // JPG
674 + 'data:image/jpe', // JPEG
675 + 'data:image/pjp', // PJPEG
676 + ], true ) ) {
677 + return true;
678 + }
679 +
680 + // Allow known short data URIs.
681 + if ( in_array( substr( $value, 0, 12 ), [
682 + 'data:img/png', // PNG
683 + 'data:img/gif', // GIF
684 + 'data:img/jpg', // JPG
685 + 'data:img/jpe', // JPEG
686 + 'data:img/pjp', // PJPEG
687 + ], true ) ) {
688 + return true;
689 + }
690 +
691 + return false;
692 + }
693 +
694 + /**
695 + * Validate Use Tag
696 + *
697 + * @since 3.16.0
698 + * @access private
699 + *
700 + * @param $element
701 + */
702 + private function validate_use_tag( $element ) {
703 + $xlinks = $element->getAttributeNS( 'http://www.w3.org/1999/xlink', 'href' );
704 + if ( $xlinks && '#' !== substr( $xlinks, 0, 1 ) ) {
705 + $element->parentNode->removeChild( $element ); // phpcs:ignore -- php DomNode
706 + }
707 + }
708 +
709 + /**
710 + * Strip Doctype
711 + *
712 + * @since 3.16.0
713 + * @access private
714 + */
715 + private function strip_doctype() {
716 + foreach ( $this->svg_dom->childNodes as $child ) {
717 + if ( XML_DOCUMENT_TYPE_NODE === $child->nodeType ) { // phpcs:ignore -- php DomDocument
718 + $child->parentNode->removeChild( $child ); // phpcs:ignore -- php DomDocument
719 + }
720 + }
721 + }
722 +
723 + /**
724 + * Sanitize Elements
725 + *
726 + * @since 3.16.0
727 + * @access private
728 + */
729 + private function sanitize_elements() {
730 + $elements = $this->svg_dom->getElementsByTagName( '*' );
731 + // loop through all elements
732 + // we do this backwards so we don't skip anything if we delete a node
733 + // see comments at: http://php.net/manual/en/class.domnamednodemap.php
734 + for ( $index = $elements->length - 1; $index >= 0; $index-- ) {
735 + /**
736 + * @var \DOMElement $current_element
737 + */
738 + $current_element = $elements->item( $index );
739 + // If the tag isn't in the whitelist, remove it and continue with next iteration
740 + if ( ! $this->is_allowed_tag( $current_element ) ) {
741 + continue;
742 + }
743 +
744 + // validate element attributes
745 + $this->validate_allowed_attributes( $current_element );
746 +
747 + $this->strip_xlinks( $current_element );
748 +
749 + if ( 'use' === strtolower( $current_element->tagName ) ) { // phpcs:ignore -- php DomDocument
750 + $this->validate_use_tag( $current_element );
751 + }
752 + }
753 + }
754 +
755 + /**
756 + * Strip PHP Tags
757 + *
758 + * @since 3.16.0
759 + * @access private
760 + *
761 + * @param string $content
762 + * @return string
763 + */
764 + private function strip_php_tags( $content ) {
765 + $content = preg_replace( '/<\?(=|php)(.+?)\?>/i', '', $content );
766 + // Remove XML, ASP, etc.
767 + $content = preg_replace( '/<\?(.*)\?>/Us', '', $content );
768 + $content = preg_replace( '/<\%(.*)\%>/Us', '', $content );
769 +
770 + if ( ( false !== strpos( $content, '<?' ) ) || ( false !== strpos( $content, '<%' ) ) ) {
771 + return '';
772 + }
773 + return $content;
774 + }
775 +
776 + /**
777 + * Strip Comments
778 + *
779 + * @since 3.16.0
780 + * @access private
781 + *
782 + * @param string $content
783 + * @return string
784 + */
785 + private function strip_comments( $content ) {
786 + // Remove comments.
787 + $content = preg_replace( '/<!--(.*)-->/Us', '', $content );
788 + $content = preg_replace( '/\/\*(.*)\*\//Us', '', $content );
789 + if ( ( false !== strpos( $content, '<!--' ) ) || ( false !== strpos( $content, '/*' ) ) ) {
790 + return '';
791 + }
792 + return $content;
793 + }
794 +
795 + /**
796 + * Strip Line Breaks
797 + *
798 + * @since 3.16.0
799 + * @access private
800 + *
801 + * @param string $content
802 + * @return string
803 + */
804 + private function strip_line_breaks( $content ) {
805 + // Remove line breaks.
806 + return preg_replace( '/\r|\n/', '', $content );
807 + }
808 + }
809 +