PluginProbe
bBlocks – Essential Gutenberg Blocks & Patterns Collection / 2.1.8
bBlocks – Essential Gutenberg Blocks & Patterns Collection v2.1.8
2.1.8 2.1.7 2.1.6 2.1.5 2.1.4 2.1.3 2.1.2 2.1.1 2.1.0 2.0.43 2.0.42 2.0.41 2.0.40 2.0.39 2.0.38 trunk 1.0 1.1 1.2 1.3 1.4 1.5 1.5.1 1.5.2 1.5.3 All 108 releases
b-blocks / includes / Sanitize.php

Sanitize.php in bBlocks – Essential Gutenberg Blocks & Patterns Collection 2.1.8, at includes/Sanitize.php

216 lines 5.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace BBlocks\Inc;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit;
7 }
8
9 class Sanitize
10 {
11
12 private static $cache = [];
13
14 public static function svg( $markup )
15 {
16 if ( ! is_string( $markup ) || '' === trim( $markup ) ) {
17 return '';
18 }
19
20 $key = md5( $markup );
21 if ( isset( self::$cache[ $key ] ) ) {
22 return self::$cache[ $key ];
23 }
24
25 $sanitizer = self::sanitizer();
26 if ( ! $sanitizer ) {
27 return self::$cache[ $key ] = '';
28 }
29
30 try {
31 $clean = $sanitizer->sanitize( $markup );
32 } catch ( \Throwable $e ) {
33 $clean = false;
34 }
35
36 if ( ! is_string( $clean ) || '' === trim( $clean ) || ! self::isSafe( $clean ) ) {
37 return self::$cache[ $key ] = '';
38 }
39
40 return self::$cache[ $key ] = $clean;
41 }
42
43 public static function markup( $text )
44 {
45 if ( ! is_string( $text ) || false === stripos( $text, '<svg' ) ) {
46 return $text;
47 }
48
49 return preg_replace_callback(
50 '#<svg\b[^>]*>.*?</svg\s*>#is',
51 static function ( $matches ) {
52 return self::svg( $matches[0] );
53 },
54 $text
55 );
56 }
57
58 /**
59 * Author-supplied raw HTML, gated on the unfiltered_html capability.
60 *
61 * WordPress' answer to "may this person publish markup that executes" is
62 * the unfiltered_html capability: Administrators have it on single sites,
63 * Super Admins have it on multisite, and Editors and below never do. This
64 * applies that same rule to block attributes that are rendered as raw HTML
65 * - the HTML block's `htmlCode` in particular - which core's kses pass on
66 * post_content does not reliably reach, because the block serializer
67 * unicode-escapes `<` inside the attribute JSON and kses sees no tag.
68 *
69 * The capability is read from the POST AUTHOR, not the current viewer: the
70 * author is who put the markup there, and the check has to give the same
71 * answer for every visitor to the page.
72 *
73 * @param string $html Raw markup from a block attribute.
74 * @param int|null $authorId Post author to test; resolved from the current
75 * post when omitted.
76 * @return string The markup unchanged, or a wp_kses_post() copy of it.
77 */
78 public static function userHtml( $html, $authorId = null )
79 {
80 if ( ! is_string( $html ) || '' === trim( $html ) ) {
81 return '';
82 }
83
84 return self::authorCanUnfilteredHtml( $authorId ) ? $html : wp_kses_post( $html );
85 }
86
87 /**
88 * Whether the post author may publish unfiltered HTML.
89 *
90 * Fails closed: if the author cannot be resolved - a block rendered
91 * outside the loop, in a template part, or in a widget area - the answer
92 * is no, and the caller sanitizes.
93 */
94 public static function authorCanUnfilteredHtml( $authorId = null )
95 {
96 if ( null === $authorId ) {
97 $postId = get_the_ID();
98 $authorId = $postId ? (int) get_post_field( 'post_author', $postId ) : 0;
99 }
100
101 $authorId = (int) $authorId;
102
103 if ( $authorId <= 0 ) {
104 return false;
105 }
106
107 return user_can( $authorId, 'unfiltered_html' );
108 }
109
110 public static function attributes( $attributes )
111 {
112 if ( is_string( $attributes ) ) {
113 return self::markup( $attributes );
114 }
115
116 if ( is_array( $attributes ) ) {
117 foreach ( $attributes as $key => $value ) {
118 $attributes[ $key ] = self::attributes( $value );
119 }
120 }
121
122 return $attributes;
123 }
124
125 public static function sanitizer()
126 {
127 static $sanitizer = null;
128 static $resolved = false;
129
130 if ( $resolved ) {
131 return $sanitizer;
132 }
133
134 $resolved = true;
135
136 if ( ! class_exists( '\enshrined\svgSanitize\Sanitizer' ) ) {
137 $autoload = B_BLOCKS_DIR_PATH . 'vendor/autoload.php';
138
139 if ( file_exists( $autoload ) ) {
140 require_once $autoload;
141 }
142 }
143
144 if ( ! class_exists( '\enshrined\svgSanitize\Sanitizer' ) ) {
145 return $sanitizer;
146 }
147
148 $instance = new \enshrined\svgSanitize\Sanitizer();
149 $instance->removeRemoteReferences( true );
150 $instance->removeXMLTag( true );
151 $instance->useThreshold( 1000 );
152 $instance->setUseNestingLimit( 5 );
153
154 $filtered = apply_filters( 'b_blocks_svg_sanitizer', $instance );
155
156 $sanitizer = $filtered instanceof \enshrined\svgSanitize\Sanitizer ? $filtered : $instance;
157
158 return $sanitizer;
159 }
160
161 public static function isSafe( $clean )
162 {
163 $forbidden = [ 'script', 'foreignobject', 'handler', 'iframe', 'embed', 'object', 'base', 'meta' ];
164
165 $previous = libxml_use_internal_errors( true );
166 $dom = new \DOMDocument();
167 $loaded = $dom->loadXML( $clean, LIBXML_NONET );
168 libxml_clear_errors();
169 libxml_use_internal_errors( $previous );
170
171 if ( ! $loaded || ! $dom->documentElement ) {
172 return false;
173 }
174 if ( 'svg' !== strtolower( $dom->documentElement->localName ) ) {
175 return false;
176 }
177
178 foreach ( ( new \DOMXPath( $dom ) )->query( '//*' ) as $element ) {
179 if ( in_array( strtolower( $element->localName ), $forbidden, true ) ) {
180 return false;
181 }
182
183 if ( 'style' === strtolower( $element->localName ) ) {
184 $css = preg_replace( '/[\s\x00-\x1f]+/', '', strtolower( (string) $element->textContent ) );
185
186 foreach ( [ 'javascript:', 'vbscript:', 'expression(', '@import' ] as $needle ) {
187 if ( false !== strpos( $css, $needle ) ) {
188 return false;
189 }
190 }
191 }
192
193 if ( ! $element->hasAttributes() ) {
194 continue;
195 }
196
197 foreach ( $element->attributes as $attribute ) {
198 if ( 0 === stripos( $attribute->nodeName, 'on' ) || 0 === stripos( (string) $attribute->localName, 'on' ) ) {
199 return false;
200 }
201
202 $value = strtolower( html_entity_decode( $attribute->nodeValue, ENT_QUOTES, 'UTF-8' ) );
203 $value = preg_replace( '/[\s\x00-\x1f]+/', '', $value );
204
205 foreach ( [ 'javascript:', 'vbscript:', 'data:text/html' ] as $needle ) {
206 if ( false !== strpos( $value, $needle ) ) {
207 return false;
208 }
209 }
210 }
211 }
212
213 return true;
214 }
215 }
216