Make WordPress Core

Changeset 43810


Ignore:
Timestamp:
10/23/2018 05:47:28 PM (6 years ago)
Author:
danielbachhuber
Message:

Embeds: Filter HTML response in oEmbed proxy controller.

Adapts the response from WP_oEmbed_Controller::get_proxy_item() so that the response is correctly filtered and embeds work properly in JavaSccript editors. Introduces new get_oembed_response_data_for_url() function for preparing internal oEmbed responses.

Props danielbachhuber, imath, swissspidy.
Fixes #45142.

Location:
branches/5.0
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • branches/5.0/src/wp-includes/class-oembed.php

    r41681 r43810  
    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 );
  • branches/5.0/src/wp-includes/class-wp-oembed-controller.php

    r41162 r43810  
    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
     
    179186            return new WP_Error( 'oembed_invalid_url', get_status_header_desc( 404 ), array( 'status' => 404 ) );
    180187        }
     188
     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 );
    181191
    182192        /**
  • branches/5.0/src/wp-includes/embed.php

    r41634 r43810  
    555555    return apply_filters( 'oembed_response_data', $data, $post, $width, $height );
    556556}
     557
     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
    557622
    558623/**
     
    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         }
    1112 
    1113         return $result;
    1114     }
    1115 
    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;
    1130 }
     1139    $data = get_oembed_response_data_for_url( $url, $args );
     1140
     1141    if ( $data ) {
     1142        return _wp_oembed_get_object()->data2html( $data, $url );
     1143    }
     1144
     1145    return $result;
     1146}
  • branches/5.0/tests/phpunit/tests/oembed/controller.php

    r41139 r43810  
    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 ) {
     
    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
     
    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
     
    5964     */
    6065    public $request_count = 0;
     66
     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;
    6173
    6274    /**
     
    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
     
    8194                    'code' => 200,
    8295                ),
    83                 'body' => wp_json_encode(
     96                'body'     => wp_json_encode(
    8497                    array(
    8598                        'version'          => '1.0',
     
    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',
     
    98111                ),
    99112            );
    100         } else {
     113        }
     114
     115        if ( $url === self::UNTRUSTED_PROVIDER_URL ) {
    101116            return array(
    102117                'response' => array(
    103                     'code' => 404,
     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 ) ) {
     125            return array(
     126                'response' => array(
     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
     
    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 );
     
    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}
  • branches/5.0/tests/qunit/fixtures/wp-api-generated.js

    r43805 r43810  
    50765076    },
    50775077    {
    5078         "author": 405,
     5078        "author": 406,
    50795079        "date": "2017-02-14T00:00:00",
    50805080        "date_gmt": "2017-02-14T00:00:00",
    5081         "id": 1527,
     5081        "id": 36717,
    50825082        "modified": "2017-02-14T00:00:00",
    50835083        "modified_gmt": "2017-02-14T00:00:00",
    5084         "parent": 1526,
    5085         "slug": "1526-revision-v1",
     5084        "parent": 36716,
     5085        "slug": "36716-revision-v1",
    50865086        "guid": {
    5087             "rendered": "http://example.org/?p=1527"
     5087            "rendered": "http://example.org/?p=36717"
    50885088        },
    50895089        "title": {
     
    50995099            "parent": [
    51005100                {
    5101                     "href": "http://example.org/index.php?rest_route=/wp/v2/posts/1526"
     5101                    "href": "http://example.org/index.php?rest_route=/wp/v2/posts/36716"
    51025102                }
    51035103            ]
     
    51315131mockedApiResponse.postAutosaves = [
    51325132    {
    5133         "author": 405,
     5133        "author": 406,
    51345134        "date": "2017-02-14T00:00:00",
    51355135        "date_gmt": "2017-02-14T00:00:00",
    5136         "id": 1528,
     5136        "id": 36718,
    51375137        "modified": "2017-02-14T00:00:00",
    51385138        "modified_gmt": "2017-02-14T00:00:00",
    5139         "parent": 1526,
    5140         "slug": "1526-autosave-v1",
     5139        "parent": 36716,
     5140        "slug": "36716-autosave-v1",
    51415141        "guid": {
    5142             "rendered": "http://example.org/?p=1528"
     5142            "rendered": "http://example.org/?p=36718"
    51435143        },
    51445144        "title": {
     
    51545154            "parent": [
    51555155                {
    5156                     "href": "http://example.org/index.php?rest_route=/wp/v2/posts/1526"
     5156                    "href": "http://example.org/index.php?rest_route=/wp/v2/posts/36716"
    51575157                }
    51585158            ]
     
    51625162
    51635163mockedApiResponse.autosave = {
    5164     "author": 405,
     5164    "author": 406,
    51655165    "date": "2017-02-14T00:00:00",
    51665166    "date_gmt": "2017-02-14T00:00:00",
    5167     "id": 1528,
     5167    "id": 36718,
    51685168    "modified": "2017-02-14T00:00:00",
    51695169    "modified_gmt": "2017-02-14T00:00:00",
    5170     "parent": 1526,
    5171     "slug": "1526-autosave-v1",
     5170    "parent": 36716,
     5171    "slug": "36716-autosave-v1",
    51725172    "guid": {
    5173         "rendered": "http://example.org/?p=1528"
     5173        "rendered": "http://example.org/?p=36718"
    51745174    },
    51755175    "title": {
     
    53365336    },
    53375337    {
    5338         "author": 405,
     5338        "author": 406,
    53395339        "date": "2017-02-14T00:00:00",
    53405340        "date_gmt": "2017-02-14T00:00:00",
    5341         "id": 1530,
     5341        "id": 36720,
    53425342        "modified": "2017-02-14T00:00:00",
    53435343        "modified_gmt": "2017-02-14T00:00:00",
    5344         "parent": 1529,
    5345         "slug": "1529-revision-v1",
     5344        "parent": 36719,
     5345        "slug": "36719-revision-v1",
    53465346        "guid": {
    5347             "rendered": "http://example.org/?p=1530"
     5347            "rendered": "http://example.org/?p=36720"
    53485348        },
    53495349        "title": {
     
    53595359            "parent": [
    53605360                {
    5361                     "href": "http://example.org/index.php?rest_route=/wp/v2/pages/1529"
     5361                    "href": "http://example.org/index.php?rest_route=/wp/v2/pages/36719"
    53625362                }
    53635363            ]
     
    53915391mockedApiResponse.pageAutosaves = [
    53925392    {
    5393         "author": 405,
     5393        "author": 406,
    53945394        "date": "2017-02-14T00:00:00",
    53955395        "date_gmt": "2017-02-14T00:00:00",
    5396         "id": 1531,
     5396        "id": 36721,
    53975397        "modified": "2017-02-14T00:00:00",
    53985398        "modified_gmt": "2017-02-14T00:00:00",
    5399         "parent": 1529,
    5400         "slug": "1529-autosave-v1",
     5399        "parent": 36719,
     5400        "slug": "36719-autosave-v1",
    54015401        "guid": {
    5402             "rendered": "http://example.org/?p=1531"
     5402            "rendered": "http://example.org/?p=36721"
    54035403        },
    54045404        "title": {
     
    54145414            "parent": [
    54155415                {
    5416                     "href": "http://example.org/index.php?rest_route=/wp/v2/pages/1529"
     5416                    "href": "http://example.org/index.php?rest_route=/wp/v2/pages/36719"
    54175417                }
    54185418            ]
     
    54225422
    54235423mockedApiResponse.pageAutosave = {
    5424     "author": 405,
     5424    "author": 406,
    54255425    "date": "2017-02-14T00:00:00",
    54265426    "date_gmt": "2017-02-14T00:00:00",
    5427     "id": 1531,
     5427    "id": 36721,
    54285428    "modified": "2017-02-14T00:00:00",
    54295429    "modified_gmt": "2017-02-14T00:00:00",
    5430     "parent": 1529,
    5431     "slug": "1529-autosave-v1",
     5430    "parent": 36719,
     5431    "slug": "36719-autosave-v1",
    54325432    "guid": {
    5433         "rendered": "http://example.org/?p=1531"
     5433        "rendered": "http://example.org/?p=36721"
    54345434    },
    54355435    "title": {
Note: See TracChangeset for help on using the changeset viewer.