Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: fixed

Show YouTube and other video players in comments on WordPress.com, and show embeds that need a script, such as TikTok, as links.
56 changes: 28 additions & 28 deletions projects/packages/comments/src/editor/class-embeds.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ private static function respond( $data ) {
unset( $data->scripts );
$data->html = self::sanitize_html( $data->html ?? '' );

// Nothing to show without the script, so the comment will hold the link. Say so now.
// No player or photo to show, so the comment will hold the link. Say so now.
if ( '' === $data->html ) {
return new WP_Error( 'oembed_invalid_url', get_status_header_desc( 404 ), array( 'status' => 404 ) );
}
Expand All @@ -196,9 +196,8 @@ private static function respond( $data ) {
}

/**
* A provider's HTML as markup alone, or '' when nothing visible is left. Its script would run
* first-party for every reader on a commenter's say-so, and so would an inline handler, so the
* HTML goes through kses with core's post rules plus the iframe the players need.
* A provider's player or photo, rebuilt as a bare iframe or img, or '' when it sent neither. Nothing else of
* its HTML is kept, so no script or inline handler runs first-party for every reader on a commenter's say-so.
*
* @param mixed $html What the provider sent.
* @return string
Expand All @@ -208,35 +207,36 @@ public static function sanitize_html( $html ) {
return '';
}

// Whole, so a script's body does not survive as text once kses drops its tags.
$html = (string) preg_replace( '#<script\b[^>]*>.*?(?:</script\s*>|$)#is', '', $html );

$allowed = wp_kses_allowed_html( 'post' );
unset( $allowed['object'] );
$allowed['iframe'] = array(
'src' => true,
'width' => true,
'height' => true,
'title' => true,
'allow' => true,
'allowfullscreen' => true,
'frameborder' => true,
'scrolling' => true,
'loading' => true,
'referrerpolicy' => true,
'sandbox' => true,
'style' => true,
'class' => true,
'name' => true,
$kept = array(
'IFRAME' => array( 'width', 'height', 'title', 'allow', 'allowfullscreen', 'frameborder', 'loading', 'referrerpolicy', 'sandbox' ),
'IMG' => array( 'width', 'height', 'alt' ),
);

$html = wp_kses( $html, $allowed );
$sent = new \WP_HTML_Tag_Processor( $html );
while ( $sent->next_tag() ) {
$name = (string) $sent->get_tag();
$src = $sent->get_attribute( 'src' );
$src = is_string( $src ) ? esc_url_raw( str_starts_with( $src, '//' ) ? 'https:' . $src : $src ) : '';

if ( ! preg_match( '#<(iframe|img|video|audio)\b#i', $html ) && '' === trim( wp_strip_all_tags( $html ) ) ) {
return '';
if ( ! isset( $kept[ $name ] ) || 'https' !== wp_parse_url( $src, PHP_URL_SCHEME ) ) {
continue;
}

// A tag of our own, so only the attributes listed above come along.
$tag = new \WP_HTML_Tag_Processor( 'IFRAME' === $name ? '<iframe></iframe>' : '<img>' );
$tag->next_tag();
$tag->set_attribute( 'src', $src );
foreach ( $kept[ $name ] as $attribute ) {
$value = $sent->get_attribute( $attribute );
if ( null !== $value ) {
$tag->set_attribute( $attribute, $value );
}
}

return $tag->get_updated_html();
}

return $html;
return '';
}

/**
Expand Down
Loading