PluginProbe
ActivityPub / 8.0.2
ActivityPub v8.0.2
9.3.1 9.3.0 9.2.2 9.2.1 9.2.0 9.1.0 9.0.2 9.0.1 9.0.0 8.3.0 8.2.1 8.2.0 8.1.1 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.2.0 1.3.0 2.0.0 2.0.1 2.1.0 2.1.1 All 160 releases
← All changes | includes/class-sanitize.php +46 -313 8.3.08.0.2 View file →
@@ -13,53 +13,9 @@
13 13 /**
14 14 * Sanitization class.
15 15 */
16 16 class Sanitize {
17 -
18 17 /**
19 - * Elements to strip including their inner content.
20 - *
21 - * WordPress's wp_kses removes disallowed tags but preserves their inner text.
22 - * These elements contain content that is meaningless or harmful
23 - * without the surrounding tag (scripts, styles, interactive UI,
24 - * embedded objects), so we remove them entirely before wp_kses runs.
25 - *
26 - * @var array<string>
27 - */
28 - const STRIP_ELEMENTS = array(
29 - 'script',
30 - 'style',
31 - 'button',
32 - 'nav',
33 - 'form',
34 - 'textarea',
35 - 'select',
36 - 'input',
37 - 'fieldset',
38 - 'iframe',
39 - 'embed',
40 - 'object',
41 - );
42 -
43 - /**
44 - * MathML global attributes allowed per the W3C MathML safe list.
45 - *
46 - * @see https://w3c.github.io/mathml-docs/mathml-safe-list
47 - *
48 - * @var array<string, true>
49 - */
50 - const MATHML_GLOBAL_ATTRS = array(
51 - 'dir' => true,
52 - 'displaystyle' => true,
53 - 'mathbackground' => true,
54 - 'mathcolor' => true,
55 - 'mathsize' => true,
56 - 'scriptlevel' => true,
57 - 'intent' => true,
58 - 'arg' => true,
59 - );
60 -
61 - /**
62 18 * Sanitize a list of URLs.
63 19 *
64 20 * @param string|array $value The value to sanitize.
65 21 * @return array The sanitized list of URLs.
@@ -260,47 +216,13 @@
260 216 return \trim( \preg_replace( '/>[\n\r\t]+</', '><', $content ) );
261 217 }
262 218
263 219 /**
264 - * Sanitize a redirect URI, preserving custom protocol schemes.
265 - *
266 - * WordPress's sanitize_url() and esc_url_raw() strip unknown protocols.
267 - * This method extracts the scheme and passes it as allowed so custom
268 - * URI schemes for native apps (RFC 8252 Section 7.1) are preserved.
269 - *
270 - * @since 8.1.0
271 - *
272 - * @param string $uri The redirect URI to sanitize.
273 - * @return string The sanitized URI.
274 - */
275 - public static function redirect_uri( $uri ) {
276 - /*
277 - * Extract scheme manually because wp_parse_url() returns false
278 - * for URIs like "myapp://" (scheme + empty authority, no path).
279 - */
280 - if ( ! preg_match( '/^([a-zA-Z][a-zA-Z0-9+.\-]*):/', $uri, $matches ) ) {
281 - return '';
282 - }
283 -
284 - $scheme = \strtolower( $matches[1] );
285 -
286 - // For standard schemes, use default sanitization.
287 - if ( in_array( $scheme, array( 'http', 'https' ), true ) ) {
288 - return \sanitize_url( $uri );
289 - }
290 -
291 - // For custom schemes, include the scheme in allowed protocols.
292 - return \sanitize_url( $uri, array_merge( \wp_allowed_protocols(), array( $scheme ) ) );
293 - }
294 -
295 - /**
296 220 * Clean HTML for ActivityPub federation.
297 221 *
298 - * Uses a positive allowlist based on FEP-b2b8 (Long-form Text) for the
299 - * `content` property, extended with common WordPress content elements.
300 - * Interactive, navigational, and scripting elements are stripped entirely.
222 + * Keeps all WordPress allowed tags but removes global attributes like
223 + * class, id, style, data-*, aria-* that increase payload size.
301 224 *
302 - * @see https://codeberg.org/fediverse/fep/src/branch/main/fep/b2b8/fep-b2b8.md
303 225 * @see https://github.com/Automattic/wordpress-activitypub/issues/2619
304 226 *
305 227 * @param string $content The HTML content to clean.
306 228 *
@@ -310,251 +232,62 @@
310 232 if ( empty( $content ) ) {
311 233 return $content;
312 234 }
313 235
314 - /*
315 - * Strip elements whose inner content is noise (scripts, styles, interactive UI, embeds).
316 - * This runs before wp_kses because wp_kses strips tags but keeps inner text,
317 - * and content inside <script>, <style>, <nav>, etc. is meaningless on its own.
318 - */
319 - $strip_pattern = \implode( '|', self::STRIP_ELEMENTS );
320 - $content = \preg_replace( '@<(' . $strip_pattern . ')[^>]*?>.*?</\\1>@si', '', $content );
321 - // Also catch self-closing variants (e.g. <input />, <embed />).
322 - $content = \preg_replace( '@<(' . $strip_pattern . ')[^>]*?/?>@si', '', $content );
236 + // Start with all WordPress allowed post tags.
237 + $allowed_html = \wp_kses_allowed_html( 'post' );
323 238
239 + // Global attributes to remove from all elements.
240 + $remove_attrs = array(
241 + 'aria-controls',
242 + 'aria-current',
243 + 'aria-describedby',
244 + 'aria-details',
245 + 'aria-expanded',
246 + 'aria-hidden',
247 + 'aria-label',
248 + 'aria-labelledby',
249 + 'aria-live',
250 + 'class',
251 + 'data-*',
252 + 'decoding',
253 + 'dir',
254 + 'hidden',
255 + 'id',
256 + 'lang',
257 + 'loading',
258 + 'role',
259 + 'style',
260 + 'tabindex',
261 + 'title',
262 + 'xml:lang',
263 + );
264 +
324 265 /**
325 - * Fires the deprecated attribute removal filter.
266 + * Filter the global attributes to remove from all elements.
326 267 *
327 - * @deprecated 8.1.0 Use the {@see 'activitypub_allowed_html'} filter instead.
268 + * @param array $remove_attrs Global attributes to remove.
328 269 */
329 - if ( \has_filter( 'activitypub_remove_html_attributes' ) ) {
330 - \_deprecated_hook( 'activitypub_remove_html_attributes', '8.1.0', 'activitypub_allowed_html' );
270 + $remove_attrs = \apply_filters( 'activitypub_remove_html_attributes', $remove_attrs );
271 +
272 + // Remove global attributes from all tags.
273 + foreach ( $allowed_html as $tag => $attrs ) {
274 + $allowed_html[ $tag ] = \array_diff_key( $attrs, \array_flip( $remove_attrs ) );
331 275 }
332 276
277 + // Re-add class and title for anchors (needed for microformats).
278 + $allowed_html['a']['class'] = true;
279 + $allowed_html['a']['title'] = true;
280 +
281 + // Re-add class for spans (needed for microformats).
282 + $allowed_html['span']['class'] = true;
283 +
333 284 /**
334 - * Filters the allowed HTML for ActivityPub content.
285 + * Filter the final allowed HTML for ActivityPub content.
335 286 *
336 - * The default allowlist is based on FEP-b2b8 (Long-form Text),
337 - * extended with common WordPress content elements like figures,
338 - * tables, definition lists, and horizontal rules.
339 - *
340 287 * @param array $allowed_html The allowed HTML structure for wp_kses.
341 288 */
342 - $allowed_html = \apply_filters( 'activitypub_allowed_html', self::get_allowed_html() );
289 + $allowed_html = \apply_filters( 'activitypub_allowed_html', $allowed_html );
343 290
344 291 return \wp_kses( $content, $allowed_html, \wp_allowed_protocols() );
345 - }
346 -
347 - /**
348 - * Returns the allowed HTML elements and attributes for ActivityPub content.
349 - *
350 - * Based on the FEP-b2b8 allowlist for the `content` property, extended
351 - * with additional WordPress content elements (figures, tables, definition
352 - * lists, horizontal rules, etc.).
353 - *
354 - * @see https://codeberg.org/fediverse/fep/src/branch/main/fep/b2b8/fep-b2b8.md
355 - *
356 - * @return array The allowed HTML structure for wp_kses.
357 - */
358 - public static function get_allowed_html() {
359 - // FEP-b2b8 core allowlist.
360 - $allowed_html = array(
361 - 'p' => array(),
362 - 'span' => array(
363 - 'class' => true,
364 - ),
365 - 'br' => array(),
366 - 'a' => array(
367 - 'href' => true,
368 - 'rel' => true,
369 - 'class' => true,
370 - 'title' => true,
371 - ),
372 - 'h1' => array(),
373 - 'h2' => array(),
374 - 'h3' => array(),
375 - 'h4' => array(),
376 - 'h5' => array(),
377 - 'h6' => array(),
378 - 'del' => array(),
379 - 'pre' => array(),
380 - 'code' => array(),
381 - 'em' => array(),
382 - 'strong' => array(),
383 - 'b' => array(),
384 - 'i' => array(),
385 - 'u' => array(),
386 - 'ul' => array(),
387 - 'ol' => array(
388 - 'start' => true,
389 - 'reversed' => true,
390 - ),
391 - 'li' => array(
392 - 'value' => true,
393 - ),
394 - 'blockquote' => array(
395 - 'cite' => true,
396 - ),
397 - 'img' => array(
398 - 'src' => true,
399 - 'alt' => true,
400 - 'title' => true,
401 - 'width' => true,
402 - 'height' => true,
403 - ),
404 - 'video' => array(
405 - 'src' => true,
406 - 'controls' => true,
407 - 'loop' => true,
408 - 'poster' => true,
409 - 'width' => true,
410 - 'height' => true,
411 - ),
412 - 'audio' => array(
413 - 'src' => true,
414 - 'controls' => true,
415 - 'loop' => true,
416 - ),
417 - 'source' => array(
418 - 'src' => true,
419 - 'type' => true,
420 - ),
421 - 'ruby' => array(),
422 - 'rt' => array(),
423 - 'rp' => array(),
424 - );
425 -
426 - // WordPress content extensions beyond FEP-b2b8.
427 - $allowed_html['figure'] = array();
428 - $allowed_html['figcaption'] = array();
429 - $allowed_html['hr'] = array();
430 - $allowed_html['div'] = array();
431 - $allowed_html['table'] = array();
432 - $allowed_html['thead'] = array();
433 - $allowed_html['tbody'] = array();
434 - $allowed_html['tfoot'] = array();
435 - $allowed_html['tr'] = array();
436 - $allowed_html['th'] = array(
437 - 'colspan' => true,
438 - 'rowspan' => true,
439 - );
440 - $allowed_html['td'] = array(
441 - 'colspan' => true,
442 - 'rowspan' => true,
443 - );
444 - $allowed_html['caption'] = array();
445 - $allowed_html['dl'] = array();
446 - $allowed_html['dt'] = array();
447 - $allowed_html['dd'] = array();
448 - $allowed_html['s'] = array();
449 - $allowed_html['sub'] = array();
450 - $allowed_html['sup'] = array();
451 - $allowed_html['abbr'] = array(
452 - 'title' => true,
453 - );
454 - $allowed_html['mark'] = array();
455 - $allowed_html['ins'] = array();
456 - $allowed_html['cite'] = array();
457 - $allowed_html['time'] = array(
458 - 'datetime' => true,
459 - );
460 - $allowed_html['track'] = array(
461 - 'src' => true,
462 - 'kind' => true,
463 - 'label' => true,
464 - 'srclang' => true,
465 - );
466 -
467 - // MathML safe elements per W3C MathML safe list.
468 - $allowed_html['math'] = \array_merge(
469 - self::MATHML_GLOBAL_ATTRS,
470 - array(
471 - 'display' => true,
472 - )
473 - );
474 - $allowed_html['merror'] = self::MATHML_GLOBAL_ATTRS;
475 - $allowed_html['mfrac'] = \array_merge(
476 - self::MATHML_GLOBAL_ATTRS,
477 - array(
478 - 'linethickness' => true,
479 - )
480 - );
481 - $allowed_html['mi'] = self::MATHML_GLOBAL_ATTRS;
482 - $allowed_html['mmultiscripts'] = self::MATHML_GLOBAL_ATTRS;
483 - $allowed_html['mn'] = self::MATHML_GLOBAL_ATTRS;
484 - $allowed_html['mo'] = \array_merge(
485 - self::MATHML_GLOBAL_ATTRS,
486 - array(
487 - 'form' => true,
488 - 'fence' => true,
489 - 'separator' => true,
490 - 'lspace' => true,
491 - 'rspace' => true,
492 - 'stretchy' => true,
493 - 'symmetric' => true,
494 - 'maxsize' => true,
495 - 'minsize' => true,
496 - 'largeop' => true,
497 - 'movablelimits' => true,
498 - )
499 - );
500 - $allowed_html['mover'] = self::MATHML_GLOBAL_ATTRS;
501 - $allowed_html['mpadded'] = \array_merge(
502 - self::MATHML_GLOBAL_ATTRS,
503 - array(
504 - 'width' => true,
505 - 'height' => true,
506 - 'depth' => true,
507 - 'lspace' => true,
508 - 'voffset' => true,
509 - )
510 - );
511 - $allowed_html['mprescripts'] = self::MATHML_GLOBAL_ATTRS;
512 - $allowed_html['mroot'] = self::MATHML_GLOBAL_ATTRS;
513 - $allowed_html['mrow'] = self::MATHML_GLOBAL_ATTRS;
514 - $allowed_html['ms'] = self::MATHML_GLOBAL_ATTRS;
515 - $allowed_html['mspace'] = \array_merge(
516 - self::MATHML_GLOBAL_ATTRS,
517 - array(
518 - 'width' => true,
519 - 'height' => true,
520 - 'depth' => true,
521 - )
522 - );
523 - $allowed_html['msqrt'] = self::MATHML_GLOBAL_ATTRS;
524 - $allowed_html['mstyle'] = self::MATHML_GLOBAL_ATTRS;
525 - $allowed_html['msub'] = self::MATHML_GLOBAL_ATTRS;
526 - $allowed_html['msubsup'] = self::MATHML_GLOBAL_ATTRS;
527 - $allowed_html['msup'] = self::MATHML_GLOBAL_ATTRS;
528 - $allowed_html['mtable'] = self::MATHML_GLOBAL_ATTRS;
529 - $allowed_html['mtd'] = \array_merge(
530 - self::MATHML_GLOBAL_ATTRS,
531 - array(
532 - 'columnspan' => true,
533 - 'rowspan' => true,
534 - )
535 - );
536 - $allowed_html['mtext'] = self::MATHML_GLOBAL_ATTRS;
537 - $allowed_html['mtr'] = self::MATHML_GLOBAL_ATTRS;
538 - $allowed_html['munder'] = self::MATHML_GLOBAL_ATTRS;
539 - $allowed_html['munderover'] = \array_merge(
540 - self::MATHML_GLOBAL_ATTRS,
541 - array(
542 - 'accent' => true,
543 - 'accentunder' => true,
544 - )
545 - );
546 - $allowed_html['semantics'] = \array_merge(
547 - self::MATHML_GLOBAL_ATTRS,
548 - array(
549 - 'encoding' => true,
550 - )
551 - );
552 - $allowed_html['annotation'] = \array_merge(
553 - self::MATHML_GLOBAL_ATTRS,
554 - array(
555 - 'encoding' => true,
556 - )
557 - );
558 - return $allowed_html;
559 292 }
560 293 }