Shortcodes: Restrict ajax handler for media shortcode.

Props tykoted, xknown, peterwilsoncc, antpb, jorbin.
Merges [56838] to the 6.3 branch.





git-svn-id: https://develop.svn.wordpress.org/branches/6.3@56846 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Jb Audras 2023-10-12 13:27:29 +00:00
parent 9c47921856
commit 82eb50b8af
4 changed files with 162 additions and 1 deletions

View File

@ -3882,13 +3882,29 @@ function wp_ajax_parse_media_shortcode() {
$shortcode = wp_unslash( $_POST['shortcode'] );
// Only process previews for media related shortcodes:
$found_shortcodes = get_shortcode_tags_in_content( $shortcode );
$media_shortcodes = array(
'audio',
'embed',
'playlist',
'video',
'gallery',
);
$other_shortcodes = array_diff( $found_shortcodes, $media_shortcodes );
if ( ! empty( $other_shortcodes ) ) {
wp_send_json_error();
}
if ( ! empty( $_POST['post_ID'] ) ) {
$post = get_post( (int) $_POST['post_ID'] );
}
// The embed shortcode requires a post.
if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
if ( 'embed' === $shortcode ) {
if ( in_array( 'embed', $found_shortcodes, true ) ) {
wp_send_json_error();
}
} else {

View File

@ -2605,6 +2605,7 @@ function gallery_shortcode( $attr ) {
$attachments[ $val->ID ] = $_attachments[ $key ];
}
} elseif ( ! empty( $atts['exclude'] ) ) {
$post_parent_id = $id;
$attachments = get_children(
array(
'post_parent' => $id,
@ -2617,6 +2618,7 @@ function gallery_shortcode( $attr ) {
)
);
} else {
$post_parent_id = $id;
$attachments = get_children(
array(
'post_parent' => $id,
@ -2629,6 +2631,17 @@ function gallery_shortcode( $attr ) {
);
}
if ( ! empty( $post_parent_id ) ) {
$post_parent = get_post( $post_parent_id );
// terminate the shortcode execution if user cannot read the post or password-protected
if (
( ! is_post_publicly_viewable( $post_parent->ID ) && ! current_user_can( 'read_post', $post_parent->ID ) )
|| post_password_required( $post_parent ) ) {
return '';
}
}
if ( empty( $attachments ) ) {
return '';
}
@ -2961,6 +2974,15 @@ function wp_playlist_shortcode( $attr ) {
$attachments = get_children( $args );
}
if ( ! empty( $args['post_parent'] ) ) {
$post_parent = get_post( $id );
// terminate the shortcode execution if user cannot read the post or password-protected
if ( ! current_user_can( 'read_post', $post_parent->ID ) || post_password_required( $post_parent ) ) {
return '';
}
}
if ( empty( $attachments ) ) {
return '';
}

View File

@ -168,6 +168,44 @@ function has_shortcode( $content, $tag ) {
return false;
}
/**
* Returns a list of registered shortcode names found in the given content.
*
* Example usage:
*
* get_shortcode_tags_in_content( '[audio src="file.mp3"][/audio] [foo] [gallery ids="1,2,3"]' );
* // array( 'audio', 'gallery' )
*
* @since 6.3.2
*
* @param string $content The content to check.
* @return string[] An array of registered shortcode names found in the content.
*/
function get_shortcode_tags_in_content( $content ) {
if ( false === strpos( $content, '[' ) ) {
return array();
}
preg_match_all( '/' . get_shortcode_regex() . '/', $content, $matches, PREG_SET_ORDER );
if ( empty( $matches ) ) {
return array();
}
$tags = array();
foreach ( $matches as $shortcode ) {
$tags[] = $shortcode[2];
if ( ! empty( $shortcode[5] ) ) {
$deep_tags = get_shortcode_tags_in_content( $shortcode[5] );
if ( ! empty( $deep_tags ) ) {
$tags = array_merge( $tags, $deep_tags );
}
}
}
return $tags;
}
/**
* Searches content for shortcodes and filter shortcodes through their hooks.
*

View File

@ -0,0 +1,85 @@
<?php
/**
* Admin Ajax functions to be tested.
*/
require_once ABSPATH . 'wp-admin/includes/ajax-actions.php';
/**
* Testing Ajax save draft functionality.
*
* @package WordPress
* @subpackage UnitTests
* @since 6.3.2
*
* @group ajax
*
* @covers ::wp_ajax_parse-media-shortcode
*/
class Tests_Ajax_wpAjaxParseMediaShortcode extends WP_Ajax_UnitTestCase {
const SHORTCODE_RETURN_VALUE = 'TEST';
private static $media_id;
public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
self::$media_id = self::factory()->attachment->create_object(
get_temp_dir() . 'canola.jpg',
0,
array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
'post_name' => 'restapi-client-fixture-attachment',
'post_title' => 'REST API Client Fixture: Attachment',
'post_date' => '2017-02-14 00:00:00',
'post_date_gmt' => '2017-02-14 00:00:00',
'post_author' => 0,
)
);
}
/**
* @dataProvider shortcode_provider
*/
public function test_parse_shortcode( array $payload, $expected ) {
add_shortcode( 'test', array( $this, 'shortcode_test' ) );
$_POST = array_merge(
array(
'action' => 'paser-media-shortcode',
'type' => '',
),
$payload
);
// Make the request.
try {
$this->_handleAjax( 'parse-media-shortcode' );
} catch ( WPAjaxDieContinueException $e ) {
unset( $e );
}
// Get the response, it is in heartbeat's response.
$response = json_decode( $this->_last_response, true );
$body = $response['data']['body'] ?? '';
if ( $body ) {
$this->assertStringNotContainsString( self::SHORTCODE_RETURN_VALUE, $body );
}
$this->assertSame( $expected['success'], $response['success'] );
}
public function shortcode_test() {
return self::SHORTCODE_RETURN_VALUE;
}
public function shortcode_provider() {
return array(
'gallery_shortcode_is_allowed' => array(
'payload' => array( 'shortcode' => '[gallery ids=" ' . self::$media_id . '"]' ),
'expected' => array( 'success' => true ),
),
'gallery_and_custom_test_shortcode_is_not_allowed' => array(
'payload' => array( 'shortcode' => '[gallery ids=" ' . self::$media_id . '"] [test]' ),
'expected' => array( 'success' => false ),
),
'custom_test_shortcode_is_not_allowed' => array(
'payload' => array( 'shortcode' => '[test]' ),
'expected' => array( 'success' => false ),
),
);
}
}