WordPress.org

Make WordPress Core

Ticket #45142: 45142.3.diff

File 45142.3.diff, 15.7 KB (added by danielbachhuber, 3 years ago)
  • src/wp-includes/class-oembed.php

    diff --git a/src/wp-includes/class-oembed.php b/src/wp-includes/class-oembed.php
    index 359171e37d..f27c085dc5 100644
    a b class WP_oEmbed { 
    405405                 *
    406406                 * @since 2.9.0
    407407                 *
    408                  * @param string $data The returned oEmbed HTML.
    409                  * @param string $url  URL of the content to be embedded.
    410                  * @param array  $args Optional arguments, usually passed from a shortcode.
     408                 * @param string|false $data The returned oEmbed HTML (false if unsafe).
     409                 * @param string       $url  URL of the content to be embedded.
     410                 * @param array        $args Optional arguments, usually passed from a shortcode.
    411411                 */
    412412                return apply_filters( 'oembed_result', $this->data2html( $data, $url ), $url, $args );
    413413        }
  • src/wp-includes/class-wp-oembed-controller.php

    diff --git a/src/wp-includes/class-wp-oembed-controller.php b/src/wp-includes/class-wp-oembed-controller.php
    index 7ee7950b07..9efeb54ed0 100644
    a b final class WP_oEmbed_Controller { 
    173173                        $args['height'] = $args['maxheight'];
    174174                }
    175175
     176                // Short-circuit process for URLs belonging to the current site.
     177                $data = get_oembed_response_data_for_url( $url, $args );
     178
     179                if ( $data ) {
     180                        return $data;
     181                }
     182
    176183                $data = _wp_oembed_get_object()->get_data( $url, $args );
    177184
    178185                if ( false === $data ) {
    179186                        return new WP_Error( 'oembed_invalid_url', get_status_header_desc( 404 ), array( 'status' => 404 ) );
    180187                }
    181188
     189                /** This filter is documented in wp-includes/class-oembed.php */
     190                $data->html = apply_filters( 'oembed_result', _wp_oembed_get_object()->data2html( (object) $data, $url ), $url, $args );
     191
    182192                /**
    183193                 * Filters the oEmbed TTL value (time to live).
    184194                 *
  • src/wp-includes/embed.php

    diff --git a/src/wp-includes/embed.php b/src/wp-includes/embed.php
    index 176988057b..efd991104a 100644
    a b function get_oembed_response_data( $post, $width ) { 
    555555        return apply_filters( 'oembed_response_data', $data, $post, $width, $height );
    556556}
    557557
     558
     559/**
     560 * Retrieves the oEmbed response data for a given URL.
     561 *
     562 * @since 5.0.0
     563 *
     564 * @param string $url  The URL that should be inspected for discovery `<link>` tags.
     565 * @param array  $args oEmbed remote get arguments.
     566 * @return object|false oEmbed response data if the URL does belong to the current site. False otherwise.
     567 */
     568function get_oembed_response_data_for_url( $url, $args ) {
     569        $switched_blog = false;
     570
     571        if ( is_multisite() ) {
     572                $url_parts = wp_parse_args( wp_parse_url( $url ), array(
     573                        'host'   => '',
     574                        'path'   => '/',
     575                ) );
     576
     577                $qv = array( 'domain' => $url_parts['host'], 'path' => '/' );
     578
     579                // In case of subdirectory configs, set the path.
     580                if ( ! is_subdomain_install() ) {
     581                        $path = explode( '/', ltrim( $url_parts['path'], '/' ) );
     582                        $path = reset( $path );
     583
     584                        if ( $path ) {
     585                                $qv['path'] = get_network()->path . $path . '/';
     586                        }
     587                }
     588
     589                $sites = get_sites( $qv );
     590                $site  = reset( $sites );
     591
     592                if ( $site && (int) $site->blog_id !== get_current_blog_id() ) {
     593                        switch_to_blog( $site->blog_id );
     594                        $switched_blog = true;
     595                }
     596        }
     597
     598        $post_id = url_to_postid( $url );
     599
     600        /** This filter is documented in wp-includes/class-wp-oembed-controller.php */
     601        $post_id = apply_filters( 'oembed_request_post_id', $post_id, $url );
     602
     603        if ( ! $post_id ) {
     604                if ( $switched_blog ) {
     605                        restore_current_blog();
     606                }
     607
     608                return false;
     609        }
     610
     611        $width = isset( $args['width'] ) ? $args['width'] : 0;
     612
     613        $data = get_oembed_response_data( $post_id, $width );
     614
     615        if ( $switched_blog ) {
     616                restore_current_blog();
     617        }
     618
     619        return $data ? (object) $data : false;
     620}
     621
     622
    558623/**
    559624 * Filters the oEmbed response data to return an iframe embed code.
    560625 *
    function the_embed_site_title() { 
    10711136 *                     Null if the URL does not belong to the current site.
    10721137 */
    10731138function wp_filter_pre_oembed_result( $result, $url, $args ) {
    1074         $switched_blog = false;
    1075 
    1076         if ( is_multisite() ) {
    1077                 $url_parts = wp_parse_args( wp_parse_url( $url ), array(
    1078                         'host'   => '',
    1079                         'path'   => '/',
    1080                 ) );
    1081 
    1082                 $qv = array( 'domain' => $url_parts['host'], 'path' => '/' );
    1083 
    1084                 // In case of subdirectory configs, set the path.
    1085                 if ( ! is_subdomain_install() ) {
    1086                         $path = explode( '/', ltrim( $url_parts['path'], '/' ) );
    1087                         $path = reset( $path );
    1088 
    1089                         if ( $path ) {
    1090                                 $qv['path'] = get_network()->path . $path . '/';
    1091                         }
    1092                 }
    1093 
    1094                 $sites = get_sites( $qv );
    1095                 $site  = reset( $sites );
    1096 
    1097                 if ( $site && (int) $site->blog_id !== get_current_blog_id() ) {
    1098                         switch_to_blog( $site->blog_id );
    1099                         $switched_blog = true;
    1100                 }
    1101         }
    1102 
    1103         $post_id = url_to_postid( $url );
    1104 
    1105         /** This filter is documented in wp-includes/class-wp-oembed-controller.php */
    1106         $post_id = apply_filters( 'oembed_request_post_id', $post_id, $url );
    1107 
    1108         if ( ! $post_id ) {
    1109                 if ( $switched_blog ) {
    1110                         restore_current_blog();
    1111                 }
     1139        $data = get_oembed_response_data_for_url( $url, $args );
    11121140
    1113                 return $result;
     1141        if ( $data ) {
     1142                return _wp_oembed_get_object()->data2html( $data, $url );
    11141143        }
    11151144
    1116         $width = isset( $args['width'] ) ? $args['width'] : 0;
    1117 
    1118         $data = get_oembed_response_data( $post_id, $width );
    1119         $data = _wp_oembed_get_object()->data2html( (object) $data, $url );
    1120 
    1121         if ( $switched_blog ) {
    1122                 restore_current_blog();
    1123         }
    1124 
    1125         if ( ! $data ) {
    1126                 return $result;
    1127         }
    1128 
    1129         return $data;
     1145        return $result;
    11301146}
  • tests/phpunit/tests/oembed/controller.php

    diff --git a/tests/phpunit/tests/oembed/controller.php b/tests/phpunit/tests/oembed/controller.php
    index 9f9aa6dd46..cae6948354 100644
    a b class Test_oEmbed_Controller extends WP_UnitTestCase { 
    1414        protected static $subscriber;
    1515        const YOUTUBE_VIDEO_ID = 'OQSNhk5ICTI';
    1616        const INVALID_OEMBED_URL = 'https://www.notreallyanoembedprovider.com/watch?v=awesome-cat-video';
     17        const UNTRUSTED_PROVIDER_URL = 'https://www.untrustedprovider.com';
    1718
    1819        public static function wpSetUpBeforeClass( $factory ) {
    1920                self::$subscriber = $factory->user->create( array(
    class Test_oEmbed_Controller extends WP_UnitTestCase { 
    4344
    4445                do_action( 'rest_api_init', $this->server );
    4546                add_filter( 'pre_http_request', array( $this, 'mock_embed_request' ), 10, 3 );
     47                add_filter( 'oembed_result', array( $this, 'filter_oembed_result' ), 10, 3 );
    4648                $this->request_count = 0;
     49
     50                $this->oembed_result_filter_count = 0;
    4751        }
    4852
    4953        public function tearDown() {
    5054                parent::tearDown();
    5155
    5256                remove_filter( 'pre_http_request', array( $this, 'mock_embed_request' ), 10 );
     57                remove_filter( 'oembed_result', array( $this, 'filter_oembed_result' ), 10 );
    5358        }
    5459
    5560        /**
    class Test_oEmbed_Controller extends WP_UnitTestCase { 
    5964         */
    6065        public $request_count = 0;
    6166
     67        /**
     68         * Count of the number of times the oembed_result filter was called.
     69         *
     70         * @var int
     71         */
     72        public $oembed_result_filter_count = 0;
     73
    6274        /**
    6375         * Intercept oEmbed requests and mock responses.
    6476         *
    class Test_oEmbed_Controller extends WP_UnitTestCase { 
    7183                unset( $preempt, $r );
    7284
    7385                $parsed_url = wp_parse_url( $url );
    74                 parse_str( $parsed_url['query'], $query_params );
     86                $query      = isset( $parsed_url['query'] ) ? $parsed_url['query'] : '';
     87                parse_str( $query, $query_params );
    7588                $this->request_count += 1;
    7689
    7790                // Mock request to YouTube Embed.
    class Test_oEmbed_Controller extends WP_UnitTestCase { 
    8093                                'response' => array(
    8194                                        'code' => 200,
    8295                                ),
    83                                 'body' => wp_json_encode(
     96                                'body'     => wp_json_encode(
    8497                                        array(
    8598                                                'version'          => '1.0',
    8699                                                'type'             => 'video',
    class Test_oEmbed_Controller extends WP_UnitTestCase { 
    90103                                                'width'            => $query_params['maxwidth'],
    91104                                                'thumbnail_height' => $query_params['maxheight'],
    92105                                                'height'           => $query_params['maxheight'],
    93                                                 'html'             => '<iframe width="' . $query_params['maxwidth'] . '" height="' . $query_params['maxheight'] . '" src="https://www.youtube.com/embed/' . self::YOUTUBE_VIDEO_ID . '?feature=oembed" frameborder="0" allowfullscreen></iframe>',
     106                                                'html'             => '<b>Unfiltered</b><iframe width="' . $query_params['maxwidth'] . '" height="' . $query_params['maxheight'] . '" src="https://www.youtube.com/embed/' . self::YOUTUBE_VIDEO_ID . '?feature=oembed" frameborder="0" allowfullscreen></iframe>',
    94107                                                'author_name'      => 'Yosemitebear62',
    95108                                                'thumbnail_url'    => 'https://i.ytimg.com/vi/' . self::YOUTUBE_VIDEO_ID . '/hqdefault.jpg',
    96109                                                'title'            => 'Yosemitebear Mountain Double Rainbow 1-8-10',
    97110                                        )
    98111                                ),
    99112                        );
    100                 } else {
     113                }
     114
     115                if ( $url === self::UNTRUSTED_PROVIDER_URL ) {
     116                        return array(
     117                                'response' => array(
     118                                        'code' => 200,
     119                                ),
     120                                'body'     => '<html><head><link rel="alternate" type="application/json+oembed" href="' . self::UNTRUSTED_PROVIDER_URL . '" /></head><body></body></html>',
     121                        );
     122                }
     123
     124                if ( ! empty( $query_params['url'] ) && false !== strpos( $query_params['url'], self::UNTRUSTED_PROVIDER_URL ) ) {
    101125                        return array(
    102126                                'response' => array(
    103                                         'code' => 404,
     127                                        'code' => 200,
     128                                ),
     129                                'body'     => wp_json_encode(
     130                                        array(
     131                                                'version'          => '1.0',
     132                                                'type'             => 'rich',
     133                                                'provider_name'    => 'Untrusted',
     134                                                'provider_url'     => self::UNTRUSTED_PROVIDER_URL,
     135                                                'html'             => '<b>Filtered</b><a href="">Unfiltered</a>',
     136                                                'author_name'      => 'Untrusted Embed Author',
     137                                                'title'            => 'Untrusted Embed',
     138                                        )
    104139                                ),
    105140                        );
    106141                }
     142
     143                return array(
     144                        'response' => array(
     145                                'code' => 404,
     146                        ),
     147                );
     148        }
     149
     150        /**
     151         * Filters 'oembed_result' to ensure correct type.
     152         *
     153         * @param string|false $data The returned oEmbed HTML.
     154         * @param string       $url  URL of the content to be embedded.
     155         * @param array        $args Optional arguments, usually passed from a shortcode.
     156         * @return string
     157         */
     158        public function filter_oembed_result( $data, $url, $args ) {
     159                if ( ! is_string( $data ) && false !== $data ) {
     160                        $this->fail( 'Unexpected type for $data.' );
     161                }
     162                $this->assertInternalType( 'string', $url );
     163                $this->assertInternalType( 'array', $args );
     164                $this->oembed_result_filter_count++;
     165                return $data;
    107166        }
    108167
    109168        function test_wp_oembed_ensure_format() {
    class Test_oEmbed_Controller extends WP_UnitTestCase { 
    510569                $data = $response->get_data();
    511570
    512571                $this->assertNotEmpty( $data );
    513                 $this->assertTrue( is_object( $data ) );
     572                $this->assertInternalType( 'object', $data );
    514573                $this->assertEquals( 'YouTube', $data->provider_name );
    515574                $this->assertEquals( 'https://i.ytimg.com/vi/' . self::YOUTUBE_VIDEO_ID . '/hqdefault.jpg', $data->thumbnail_url );
    516575                $this->assertEquals( $data->width, $request['maxwidth'] );
    class Test_oEmbed_Controller extends WP_UnitTestCase { 
    552611                $data = $response->get_data();
    553612                $this->assertEquals( $data['code'], 'rest_invalid_param' );
    554613        }
     614
     615        /**
     616         * @ticket 45142
     617         */
     618        function test_proxy_with_internal_url() {
     619                wp_set_current_user( self::$editor );
     620
     621                $user = self::factory()->user->create_and_get( array(
     622                        'display_name' => 'John Doe',
     623                ) );
     624                $post = self::factory()->post->create_and_get( array(
     625                        'post_author' => $user->ID,
     626                        'post_title'  => 'Hello World',
     627                ) );
     628
     629                $request = new WP_REST_Request( 'GET', '/oembed/1.0/proxy' );
     630                $request->set_param( 'url', get_permalink( $post->ID ) );
     631                $request->set_param( 'maxwidth', 400 );
     632
     633                $response = $this->server->dispatch( $request );
     634                $data     = $response->get_data();
     635
     636                $data = (array) $data;
     637
     638                $this->assertNotEmpty( $data );
     639
     640                $this->assertArrayHasKey( 'version', $data );
     641                $this->assertArrayHasKey( 'provider_name', $data );
     642                $this->assertArrayHasKey( 'provider_url', $data );
     643                $this->assertArrayHasKey( 'author_name', $data );
     644                $this->assertArrayHasKey( 'author_url', $data );
     645                $this->assertArrayHasKey( 'title', $data );
     646                $this->assertArrayHasKey( 'type', $data );
     647                $this->assertArrayHasKey( 'width', $data );
     648
     649                $this->assertEquals( '1.0', $data['version'] );
     650                $this->assertEquals( get_bloginfo( 'name' ), $data['provider_name'] );
     651                $this->assertEquals( get_home_url(), $data['provider_url'] );
     652                $this->assertEquals( $user->display_name, $data['author_name'] );
     653                $this->assertEquals( get_author_posts_url( $user->ID, $user->user_nicename ), $data['author_url'] );
     654                $this->assertEquals( $post->post_title, $data['title'] );
     655                $this->assertEquals( 'rich', $data['type'] );
     656                $this->assertTrue( $data['width'] <= $request['maxwidth'] );
     657        }
     658
     659        /**
     660         * @ticket 45142
     661         */
     662        function test_proxy_with_static_front_page_url() {
     663                wp_set_current_user( self::$editor );
     664
     665                $post = self::factory()->post->create_and_get( array(
     666                        'post_title'  => 'Front page',
     667                        'post_type'   => 'page',
     668                        'post_author' => 0,
     669                ) );
     670
     671                update_option( 'show_on_front', 'page' );
     672                update_option( 'page_on_front', $post->ID );
     673
     674                $request = new WP_REST_Request( 'GET', '/oembed/1.0/proxy' );
     675                $request->set_param( 'url', home_url() );
     676                $request->set_param( 'maxwidth', 400 );
     677
     678                $response = $this->server->dispatch( $request );
     679                $data     = $response->get_data();
     680
     681                $this->assertInternalType( 'object', $data );
     682
     683                $data = (array) $data;
     684
     685                $this->assertNotEmpty( $data );
     686
     687                $this->assertArrayHasKey( 'version', $data );
     688                $this->assertArrayHasKey( 'provider_name', $data );
     689                $this->assertArrayHasKey( 'provider_url', $data );
     690                $this->assertArrayHasKey( 'author_name', $data );
     691                $this->assertArrayHasKey( 'author_url', $data );
     692                $this->assertArrayHasKey( 'title', $data );
     693                $this->assertArrayHasKey( 'type', $data );
     694                $this->assertArrayHasKey( 'width', $data );
     695
     696                $this->assertEquals( '1.0', $data['version'] );
     697                $this->assertEquals( get_bloginfo( 'name' ), $data['provider_name'] );
     698                $this->assertEquals( get_home_url(), $data['provider_url'] );
     699                $this->assertEquals( get_bloginfo( 'name' ), $data['author_name'] );
     700                $this->assertEquals( get_home_url(), $data['author_url'] );
     701                $this->assertEquals( $post->post_title, $data['title'] );
     702                $this->assertEquals( 'rich', $data['type'] );
     703                $this->assertTrue( $data['width'] <= $request['maxwidth'] );
     704
     705                update_option( 'show_on_front', 'posts' );
     706        }
     707
     708        /**
     709         * @ticket 45142
     710         */
     711        public function test_proxy_filters_result_of_untrusted_oembed_provider() {
     712                wp_set_current_user( self::$editor );
     713
     714                $request = new WP_REST_Request( 'GET', '/oembed/1.0/proxy' );
     715                $request->set_param( 'url', self::UNTRUSTED_PROVIDER_URL );
     716                $request->set_param( 'maxwidth', 456 );
     717                $request->set_param( 'maxheight', 789 );
     718                $request->set_param( '_wpnonce', wp_create_nonce( 'wp_rest' ) );
     719
     720                $response = $this->server->dispatch( $request );
     721                $data     = $response->get_data();
     722
     723                $this->assertEquals( 1, $this->oembed_result_filter_count );
     724                $this->assertInternalType( 'object', $data );
     725                $this->assertEquals( 'Untrusted', $data->provider_name );
     726                $this->assertEquals( self::UNTRUSTED_PROVIDER_URL, $data->provider_url );
     727                $this->assertEquals( 'rich', $data->type );
     728                $this->assertFalse( $data->html );
     729        }
     730
     731        /**
     732         * @ticket 45142
     733         */
     734        public function test_proxy_does_not_filter_result_of_trusted_oembed_provider() {
     735                wp_set_current_user( self::$editor );
     736
     737                $request = new WP_REST_Request( 'GET', '/oembed/1.0/proxy' );
     738                $request->set_param( 'url', 'https://www.youtube.com/watch?v=' . self::YOUTUBE_VIDEO_ID );
     739                $request->set_param( 'maxwidth', 456 );
     740                $request->set_param( 'maxheight', 789 );
     741                $request->set_param( '_wpnonce', wp_create_nonce( 'wp_rest' ) );
     742
     743                $response = $this->server->dispatch( $request );
     744                $data     = $response->get_data();
     745
     746                $this->assertEquals( 1, $this->oembed_result_filter_count );
     747                $this->assertInternalType( 'object', $data );
     748
     749                $this->assertStringStartsWith( '<b>Unfiltered</b>', $data->html );
     750        }
    555751}