WordPress.org

Make WordPress Core

Changeset 49329


Ignore:
Timestamp:
10/27/2020 04:42:38 PM (9 months ago)
Author:
TimothyBlynJacobs
Message:

REST API: Support a broader range of JSON media types.

Previously, we only supported application/json which prevented using subtypes like application/activity+json. This allows for the REST API to json_decode the body of requests using a JSON subtype Content-Type. Additionally, wp_die() now properly sends the error as JSON when a JSON subtype is specified in the Accept header.

Props pfefferle.
Fixes #49404.

Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/load.php

    r49163 r49329  
    15961596function wp_is_json_request() {
    15971597
    1598     if ( isset( $_SERVER['HTTP_ACCEPT'] ) && false !== strpos( $_SERVER['HTTP_ACCEPT'], 'application/json' ) ) {
     1598    if ( isset( $_SERVER['HTTP_ACCEPT'] ) && wp_is_json_media_type( $_SERVER['HTTP_ACCEPT'] ) ) {
    15991599        return true;
    16001600    }
    16011601
    1602     if ( isset( $_SERVER['CONTENT_TYPE'] ) && 'application/json' === $_SERVER['CONTENT_TYPE'] ) {
     1602    if ( isset( $_SERVER['CONTENT_TYPE'] ) && wp_is_json_media_type( $_SERVER['CONTENT_TYPE'] ) ) {
    16031603        return true;
    16041604    }
     
    16341634    return $jsonp_enabled;
    16351635
     1636}
     1637
     1638/**
     1639 * Checks whether a string is a valid JSON Media Type.
     1640 *
     1641 * @since 5.6.0
     1642 *
     1643 * @param string $media_type A Media Type string to check.
     1644 * @return bool True if string is a valid JSON Media Type.
     1645 */
     1646function wp_is_json_media_type( $media_type ) {
     1647    static $cache = array();
     1648
     1649    if ( ! isset( $cache[ $media_type ] ) ) {
     1650        $cache[ $media_type ] = (bool) preg_match( '/(^|\s|,)application\/([\w!#\$&-\^\.\+]+\+)?json(\+oembed)?($|\s|;|,)/i', $media_type );
     1651    }
     1652
     1653    return $cache[ $media_type ];
    16361654}
    16371655
  • trunk/src/wp-includes/rest-api/class-wp-rest-request.php

    r48945 r49329  
    325325
    326326    /**
     327     * Checks if the request has specified a JSON content-type.
     328     *
     329     * @since 5.6.0
     330     *
     331     * @return bool True if the content-type header is JSON.
     332     */
     333    public function is_json_content_type() {
     334        $content_type = $this->get_content_type();
     335
     336        return isset( $content_type['value'] ) && wp_is_json_media_type( $content_type['value'] );
     337    }
     338
     339    /**
    327340     * Retrieves the parameter priority order.
    328341     *
     
    336349        $order = array();
    337350
    338         $content_type = $this->get_content_type();
    339         if ( isset( $content_type['value'] ) && 'application/json' === $content_type['value'] ) {
     351        if ( $this->is_json_content_type() ) {
    340352            $order[] = 'JSON';
    341353        }
     
    659671
    660672        // Check that we actually got JSON.
    661         $content_type = $this->get_content_type();
    662 
    663         if ( empty( $content_type ) || 'application/json' !== $content_type['value'] ) {
     673        if ( ! $this->is_json_content_type() ) {
    664674            return true;
    665675        }
  • trunk/tests/phpunit/tests/functions.php

    r49027 r49329  
    17291729        );
    17301730    }
     1731
     1732    /**
     1733     * @ticket 49404
     1734     * @dataProvider data_test_wp_is_json_media_type
     1735     */
     1736    public function test_wp_is_json_media_type( $input, $expected ) {
     1737        $this->assertEquals( $expected, wp_is_json_media_type( $input ) );
     1738    }
     1739
     1740
     1741    public function data_test_wp_is_json_media_type() {
     1742        return array(
     1743            array( 'application/ld+json', true ),
     1744            array( 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', true ),
     1745            array( 'application/activity+json', true ),
     1746            array( 'application/json+oembed', true ),
     1747            array( 'application/json', true ),
     1748            array( 'application/nojson', false ),
     1749            array( 'application/no.json', false ),
     1750            array( 'text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8', false ),
     1751            array( 'application/activity+json, application/nojson', true ),
     1752        );
     1753    }
    17311754}
  • trunk/tests/phpunit/tests/rest-api/rest-request.php

    r48945 r49329  
    177177        // JSON shouldn't be parsed.
    178178        $this->assertEmpty( $this->request->get_param( 'has_json_params' ) );
     179    }
     180
     181    public static function alternate_json_content_type_provider() {
     182        return array(
     183            array( 'application/ld+json', 'json', true ),
     184            array( 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', 'json', true ),
     185            array( 'application/activity+json', 'json', true ),
     186            array( 'application/json+oembed', 'json', true ),
     187            array( 'application/nojson', 'body', false ),
     188            array( 'application/no.json', 'body', false ),
     189        );
     190    }
     191
     192    /**
     193     * @ticket 49404
     194     * @dataProvider alternate_json_content_type_provider
     195     *
     196     * @param string  $content_type The content-type
     197     * @param string  $source The source value.
     198     * @param boolean $accept_json The accept_json value.
     199     */
     200    public function test_alternate_json_content_type( $content_type, $source, $accept_json ) {
     201        $this->request_with_parameters();
     202
     203        $this->request->set_method( 'POST' );
     204        $this->request->set_header( 'Content-Type', $content_type );
     205        $this->request->set_attributes( array( 'accept_json' => true ) );
     206
     207        // Check that JSON takes precedence.
     208        $this->assertEquals( $source, $this->request->get_param( 'source' ) );
     209        $this->assertEquals( $accept_json, $this->request->get_param( 'has_json_params' ) );
     210    }
     211
     212    public static function is_json_content_type_provider() {
     213        return array(
     214            array( 'application/ld+json', true ),
     215            array( 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', true ),
     216            array( 'application/activity+json', true ),
     217            array( 'application/json+oembed', true ),
     218            array( 'application/nojson', false ),
     219            array( 'application/no.json', false ),
     220        );
     221    }
     222
     223    /**
     224     * @ticket 49404
     225     * @dataProvider is_json_content_type_provider
     226     *
     227     * @param string  $content_type The content-type
     228     * @param boolean $is_json The is_json value.
     229     */
     230    public function test_is_json_content_type( $content_type, $is_json ) {
     231        $this->request_with_parameters();
     232
     233        $this->request->set_header( 'Content-Type', $content_type );
     234
     235        // Check for JSON content-type.
     236        $this->assertEquals( $is_json, $this->request->is_json_content_type() );
     237    }
     238
     239    /**
     240     * @ticket 49404
     241     */
     242    public function test_content_type_cache() {
     243        $this->request_with_parameters();
     244        $this->assertFalse( $this->request->is_json_content_type() );
     245
     246        $this->request->set_header( 'Content-Type', 'application/json' );
     247        $this->assertTrue( $this->request->is_json_content_type() );
     248
     249        $this->request->set_header( 'Content-Type', 'application/activity+json' );
     250        $this->assertTrue( $this->request->is_json_content_type() );
     251
     252        $this->request->set_header( 'Content-Type', 'application/nojson' );
     253        $this->assertFalse( $this->request->is_json_content_type() );
     254
     255        $this->request->set_header( 'Content-Type', 'application/json' );
     256        $this->assertTrue( $this->request->is_json_content_type() );
     257
     258        $this->request->remove_header( 'Content-Type' );
     259        $this->assertFalse( $this->request->is_json_content_type() );
    179260    }
    180261
Note: See TracChangeset for help on using the changeset viewer.