Make WordPress Core

Changeset 52084


Ignore:
Timestamp:
11/09/2021 10:37:19 PM (3 years ago)
Author:
hellofromTonya
Message:

HTTP API: Introduce 'http_allowed_safe_ports' filter in wp_http_validate_url().

Adds a new filter 'http_allowed_safe_ports' to control which ports are allowed for remote requests. By default, ports 80, 443, and 8080 are allowed for safe remote requests.

Adds tests.

Follow-up to [24480].

Props xknown, johnbillion, jorbin, costdev, dd32.
Fixes #54331.

Location:
trunk
Files:
2 edited

Legend:

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

    r49108 r52084  
    515515 */
    516516function wp_http_validate_url( $url ) {
     517    if ( ! is_string( $url ) || '' === $url || is_numeric( $url ) ) {
     518        return false;
     519    }
     520
    517521    $original_url = $url;
    518522    $url          = wp_kses_bad_protocol( $url, array( 'http', 'https' ) );
     
    535539
    536540    $parsed_home = parse_url( get_option( 'home' ) );
    537 
    538     if ( isset( $parsed_home['host'] ) ) {
    539         $same_host = strtolower( $parsed_home['host'] ) === strtolower( $parsed_url['host'] );
    540     } else {
    541         $same_host = false;
    542     }
     541    $same_host   = isset( $parsed_home['host'] ) && strtolower( $parsed_home['host'] ) === strtolower( $parsed_url['host'] );
     542    $host        = trim( $parsed_url['host'], '.' );
    543543
    544544    if ( ! $same_host ) {
    545         $host = trim( $parsed_url['host'], '.' );
    546545        if ( preg_match( '#^(([1-9]?\d|1\d\d|25[0-5]|2[0-4]\d)\.){3}([1-9]?\d|1\d\d|25[0-5]|2[0-4]\d)$#', $host ) ) {
    547546            $ip = $host;
     
    582581
    583582    $port = $parsed_url['port'];
    584     if ( 80 === $port || 443 === $port || 8080 === $port ) {
     583
     584    /**
     585     * Controls the list of ports considered safe in HTTP API.
     586     *
     587     * Allows to change and allow external requests for the HTTP request.
     588     *
     589     * @since 5.9.0
     590     *
     591     * @param array  $allowed_ports Array of integers for valid ports.
     592     * @param string $host          Host name of the requested URL.
     593     * @param string $url           Requested URL.
     594     */
     595    $allowed_ports = apply_filters( 'http_allowed_safe_ports', array( 80, 443, 8080 ), $host, $url );
     596    if ( in_array( $port, $allowed_ports, true ) ) {
    585597        return $url;
    586598    }
  • trunk/tests/phpunit/tests/http/http.php

    r52010 r52084  
    393393    }
    394394
     395    /**
     396     * Test that wp_http_validate_url validates URLs.
     397     *
     398     * @ticket 54331
     399     *
     400     * @dataProvider data_wp_http_validate_url_should_validate
     401     *
     402     * @covers ::wp_http_validate_url
     403     *
     404     * @param string       $url            The URL to validate.
     405     * @param false|string $cb_safe_ports  The name of the callback to http_allowed_safe_ports or false if none.
     406     *                                     Default false.
     407     * @param bool         $external_host  Whether or not the host is external.
     408     *                                     Default false.
     409     */
     410    public function test_wp_http_validate_url_should_validate( $url, $cb_safe_ports = false, $external_host = false ) {
     411        if ( $external_host ) {
     412            add_filter( 'http_request_host_is_external', '__return_true' );
     413        }
     414
     415        if ( $cb_safe_ports ) {
     416            add_filter( 'http_allowed_safe_ports', array( $this, $cb_safe_ports ) );
     417        }
     418
     419        $this->assertSame( $url, wp_http_validate_url( $url ) );
     420    }
     421
     422    /**
     423     * Data provider.
     424     *
     425     * @return array
     426     */
     427    public function data_wp_http_validate_url_should_validate() {
     428        return array(
     429            'no port specified'                 => array(
     430                'url' => 'http://example.com/caniload.php',
     431            ),
     432            'an external request when allowed'  => array(
     433                'url'           => 'http://172.20.0.123/caniload.php',
     434                'cb_safe_ports' => false,
     435                'external_host' => true,
     436            ),
     437            'a port considered safe by default' => array(
     438                'url' => 'https://example.com:8080/caniload.php',
     439            ),
     440            'a port considered safe by filter'  => array(
     441                'url'           => 'https://example.com:81/caniload.php',
     442                'cb_safe_ports' => 'callback_custom_safe_ports',
     443            ),
     444        );
     445    }
     446
     447    /**
     448     * Tests that wp_http_validate_url validates a url that uses an unsafe port
     449     * but which matches the host and port used by the site's home url.
     450     *
     451     * @ticket 54331
     452     *
     453     * @covers ::wp_http_validate_url
     454     */
     455    public function test_wp_http_validate_url_should_validate_with_an_unsafe_port_when_the_host_and_port_match_the_home_url() {
     456        $original_home    = get_option( 'home' );
     457        $home_parsed      = parse_url( $original_home );
     458        $home_scheme_host = implode( '://', array_slice( $home_parsed, 0, 2 ) );
     459        $home_modified    = $home_scheme_host . ':83';
     460
     461        update_option( 'home', $home_modified );
     462
     463        $url = $home_modified . '/caniload.php';
     464        $this->assertSame( $url, wp_http_validate_url( $url ) );
     465
     466        update_option( 'home', $original_home );
     467    }
     468
     469    /**
     470     * Test that wp_http_validate_url does not validate invalid URLs.
     471     *
     472     * @ticket 54331
     473     *
     474     * @dataProvider data_wp_http_validate_url_should_not_validate
     475     *
     476     * @covers ::wp_http_validate_url
     477     *
     478     * @param string       $url            The URL to validate.
     479     * @param false|string $cb_safe_ports  The name of the callback to http_allowed_safe_ports or false if none.
     480     *                                     Default false.
     481     * @param bool         $external_host  Whether or not the host is external.
     482     *                                     Default false.
     483     */
     484    public function test_wp_http_validate_url_should_not_validate( $url, $cb_safe_ports = false, $external_host = false ) {
     485        if ( $external_host ) {
     486            add_filter( 'http_request_host_is_external', '__return_true' );
     487        }
     488
     489        if ( $cb_safe_ports ) {
     490            add_filter( 'http_allowed_safe_ports', array( $this, $cb_safe_ports ) );
     491        }
     492
     493        $this->assertFalse( wp_http_validate_url( $url ) );
     494    }
     495
     496    /**
     497     * Data provider.
     498     *
     499     * @return array
     500     */
     501    public function data_wp_http_validate_url_should_not_validate() {
     502        return array(
     503            'url as false'                                 => array(
     504                'url' => false,
     505            ),
     506            'url as null'                                  => array(
     507                'url' => null,
     508            ),
     509            'url as int 0'                                 => array(
     510                'url' => 0,
     511            ),
     512            'url as string 0'                              => array(
     513                'url' => '0',
     514            ),
     515            'url as int 1'                                 => array(
     516                'url' => 1,
     517            ),
     518            'url as string 1'                              => array(
     519                'url' => '1',
     520            ),
     521            'url as array()'                               => array(
     522                'url' => array(),
     523            ),
     524            'an empty url'                                 => array(
     525                'url' => '',
     526            ),
     527            'a url with a non-http/https protocol'         => array(
     528                'url' => 'ftp://example.com:81/caniload.php',
     529            ),
     530            'a malformed url'                              => array(
     531                'url' => 'http:///example.com:81/caniload.php',
     532            ),
     533            'a host that cannot be parsed'                 => array(
     534                'url' => 'http:example.com/caniload.php',
     535            ),
     536            'login information'                            => array(
     537                'url' => 'http://user:pass@example.com/caniload.php',
     538            ),
     539            'a host with invalid characters'               => array(
     540                'url' => 'http://[exam]ple.com/caniload.php',
     541            ),
     542            'a host whose IPv4 address cannot be resolved' => array(
     543                'url' => 'http://exampleeeee.com/caniload.php',
     544            ),
     545            'an external request when not allowed'         => array(
     546                'url'           => 'http://192.168.0.1/caniload.php',
     547                'external_host' => false,
     548            ),
     549            'a port not considered safe by default'        => array(
     550                'url' => 'https://example.com:81/caniload.php',
     551            ),
     552            'a port not considered safe by filter'         => array(
     553                'url'           => 'https://example.com:82/caniload.php',
     554                'cb_safe_ports' => 'callback_custom_safe_ports',
     555            ),
     556            'all safe ports removed by filter'             => array(
     557                'url'           => 'https://example.com:81/caniload.php',
     558                'cb_safe_ports' => 'callback_remove_safe_ports',
     559            ),
     560        );
     561    }
     562
     563    public function callback_custom_safe_ports( $ports ) {
     564        return array( 81, 444, 8081 );
     565    }
     566
     567    public function callback_remove_safe_ports( $ports ) {
     568        return array();
     569    }
    395570}
Note: See TracChangeset for help on using the changeset viewer.