Make WordPress Core

Changeset 34674


Ignore:
Timestamp:
09/29/2015 12:40:30 AM (9 years ago)
Author:
johnbillion
Message:

Avoid stripping square brackets from URLs, and instead correctly encode them.

Square brackets must be encoded in the path, path parameters, query parameters, and fragment, but must not be encoded in anything up to the domain and port.

Adds a bunch of tests, including square brackets in query parameters, IPv6 URLs, and several other permutations.

See #16859

Location:
trunk
Files:
2 edited

Legend:

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

    r34377 r34674  
    20782078    $dest = $matches[2];
    20792079    $dest = 'http://' . $dest;
    2080     $dest = esc_url($dest);
    2081     if ( empty($dest) )
    2082         return $matches[0];
    20832080
    20842081    // removed trailing [.,;:)] from URL
     
    20872084        $dest = substr($dest, 0, strlen($dest)-1);
    20882085    }
     2086
     2087    $dest = esc_url($dest);
     2088    if ( empty($dest) )
     2089        return $matches[0];
     2090
    20892091    return $matches[1] . "<a href=\"$dest\" rel=\"nofollow\">$dest</a>$ret";
    20902092}
     
    32793281 * is applied to the returned cleaned URL.
    32803282 *
     3283 * See RFC3986
     3284 *
    32813285 * @since 2.8.0
    32823286 *
     
    32943298
    32953299    $url = str_replace( ' ', '%20', $url );
    3296     $url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\\x80-\\xff]|i', '', $url);
     3300    $url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\[\]\\x80-\\xff]|i', '', $url);
    32973301
    32983302    if ( '' === $url ) {
     
    33073311    $url = str_replace(';//', '://', $url);
    33083312    /* If the URL doesn't appear to contain a scheme, we
    3309      * presume it needs http:// appended (unless a relative
     3313     * presume it needs http:// prepended (unless a relative
    33103314     * link starting with /, # or ? or a php file).
    33113315     */
     
    33193323        $url = str_replace( '&amp;', '&#038;', $url );
    33203324        $url = str_replace( "'", '&#039;', $url );
     3325    }
     3326
     3327    if ( ( false !== strpos( $url, '[' ) ) || ( false !== strpos( $url, ']' ) ) ) {
     3328
     3329        $parsed = parse_url( $url );
     3330        $front  = '';
     3331
     3332        if ( isset( $parsed['scheme'] ) ) {
     3333            $front .= $parsed['scheme'] . '://';
     3334        } elseif ( '/' === $url[0] ) {
     3335            $front .= '//';
     3336        }
     3337
     3338        if ( isset( $parsed['user'] ) ) {
     3339            $front .= $parsed['user'];
     3340        }
     3341
     3342        if ( isset( $parsed['pass'] ) ) {
     3343            $front .= ':' . $parsed['pass'];
     3344        }
     3345
     3346        if ( isset( $parsed['user'] ) || isset( $parsed['pass'] ) ) {
     3347            $front .= '@';
     3348        }
     3349
     3350        if ( isset( $parsed['host'] ) ) {
     3351            $front .= $parsed['host'];
     3352        }
     3353
     3354        if ( isset( $parsed['port'] ) ) {
     3355            $front .= ':' . $parsed['port'];
     3356        }
     3357
     3358        $end_dirty = str_replace( $front, '', $url );
     3359        $end_clean = str_replace( array( '[', ']' ), array( '%5B', '%5D' ), $end_dirty );
     3360        $url       = str_replace( $end_dirty, $end_clean, $url );
     3361
    33213362    }
    33223363
  • trunk/tests/phpunit/tests/formatting/EscUrl.php

    r33923 r34674  
    4141
    4242    function test_all_url_parts() {
    43         $url = 'https://user:password@host.example.com:1234/path;p=1?q=2&r=3#fragment';
    44         $this->assertEquals( $url, esc_url_raw( $url ) );
    45 
    46         $this->assertEquals( 'https://user:password@host.example.com:1234/path;p=1?q=2&#038;r=3#fragment', esc_url( $url ) );
    47 
    48         $this->assertEquals( 'http://example.com?foo', esc_url( 'http://example.com?foo' ) );
     43        $url = 'https://user:pass@host.example.com:1234/path;p=1?query=2&r[]=3#fragment';
     44
     45        $this->assertEquals( array(
     46            'scheme'   => 'https',
     47            'host'     => 'host.example.com',
     48            'port'     => 1234,
     49            'user'     => 'user',
     50            'pass'     => 'pass',
     51            'path'     => '/path;p=1',
     52            'query'    => 'query=2&r[]=3',
     53            'fragment' => 'fragment',
     54        ), parse_url( $url ) );
     55        $this->assertEquals( 'https://user:pass@host.example.com:1234/path;p=1?query=2&r%5B%5D=3#fragment', esc_url_raw( $url ) );
     56        $this->assertEquals( 'https://user:pass@host.example.com:1234/path;p=1?query=2&#038;r%5B%5D=3#fragment', esc_url( $url ) );
     57    }
     58
     59    function test_all_url_parts_ipv6() {
     60        $url = 'https://user:pass@[::FFFF::127.0.0.1]:1234/path;p=1?query=2&r[]=3#fragment';
     61
     62        $this->assertEquals( array(
     63            'scheme'   => 'https',
     64            'host'     => '[::FFFF::127.0.0.1]',
     65            'port'     => 1234,
     66            'user'     => 'user',
     67            'pass'     => 'pass',
     68            'path'     => '/path;p=1',
     69            'query'    => 'query=2&r[]=3',
     70            'fragment' => 'fragment',
     71        ), parse_url( $url ) );
     72        $this->assertEquals( 'https://user:pass@[::FFFF::127.0.0.1]:1234/path;p=1?query=2&r%5B%5D=3#fragment', esc_url_raw( $url ) );
     73        $this->assertEquals( 'https://user:pass@[::FFFF::127.0.0.1]:1234/path;p=1?query=2&#038;r%5B%5D=3#fragment', esc_url( $url ) );
    4974    }
    5075
    5176    function test_bare() {
     77        $this->assertEquals( 'http://example.com?foo', esc_url( 'example.com?foo' ) );
    5278        $this->assertEquals( 'http://example.com', esc_url( 'example.com' ) );
    5379        $this->assertEquals( 'http://localhost', esc_url( 'localhost' ) );
     
    127153
    128154    /**
     155     * @ticket 16859
     156     */
     157    function test_square_brackets() {
     158        $this->assertEquals( '/example.php?one%5B%5D=two', esc_url( '/example.php?one[]=two' ) );
     159        $this->assertEquals( '?foo%5Bbar%5D=baz', esc_url( '?foo[bar]=baz' ) );
     160        $this->assertEquals( '//example.com/?foo%5Bbar%5D=baz', esc_url( '//example.com/?foo[bar]=baz' ) );
     161        $this->assertEquals( 'http://example.com/?foo%5Bbar%5D=baz', esc_url( 'example.com/?foo[bar]=baz' ) );
     162        $this->assertEquals( 'http://localhost?foo%5Bbar%5D=baz', esc_url( 'localhost?foo[bar]=baz' ) );
     163        $this->assertEquals( 'http://user:pass@localhost/?foo%5Bbar%5D=baz', esc_url( 'http://user:pass@localhost/?foo[bar]=baz' ) );
     164        $this->assertEquals( 'http://localhost?foo%5Bbar%5D=baz', esc_url( 'localhost?foo[bar]=baz' ) );
     165        $this->assertEquals( 'http://example.com/?foo%5Bbar%5D=baz', esc_url( 'http://example.com/?foo[bar]=baz' ) );
     166        $this->assertEquals( 'http://example.com:1234/?foo%5Bbar%5D=baz', esc_url( 'http://example.com:1234/?foo[bar]=baz' ) );
     167        $this->assertEquals( 'http://example.com/?foo%5Bbar%5D=baz', esc_url( 'http://example.com/?foo%5Bbar%5D=baz' ) );
     168        $this->assertEquals( 'http://example.com/?baz=bar&#038;foo%5Bbar%5D=baz', esc_url( 'http://example.com/?baz=bar&foo[bar]=baz' ) );
     169        $this->assertEquals( 'http://example.com/?baz=bar&#038;foo%5Bbar%5D=baz', esc_url( 'http://example.com/?baz=bar&#038;foo%5Bbar%5D=baz' ) );
     170    }
     171
     172    /**
     173     * @ticket 16859
     174     */
     175    function test_ipv6_hosts() {
     176        $this->assertEquals( '//[::127.0.0.1]', esc_url( '//[::127.0.0.1]' ) );
     177        $this->assertEquals( 'http://[::FFFF::127.0.0.1]', esc_url( 'http://[::FFFF::127.0.0.1]' ) );
     178        $this->assertEquals( 'http://[::127.0.0.1]', esc_url( 'http://[::127.0.0.1]' ) );
     179        $this->assertEquals( 'http://[::DEAD:BEEF:DEAD:BEEF:DEAD:BEEF:DEAD:BEEF]', esc_url( 'http://[::DEAD:BEEF:DEAD:BEEF:DEAD:BEEF:DEAD:BEEF]' ) );
     180
     181        // IPv6 with square brackets in the query? Why not.
     182        $this->assertEquals( '//[::FFFF::127.0.0.1]/?foo%5Bbar%5D=baz', esc_url( '//[::FFFF::127.0.0.1]/?foo[bar]=baz' ) );
     183        $this->assertEquals( 'http://[::FFFF::127.0.0.1]/?foo%5Bbar%5D=baz', esc_url( 'http://[::FFFF::127.0.0.1]/?foo[bar]=baz' ) );
     184    }
     185
     186    /**
     187     * Courtesy of http://blog.lunatech.com/2009/02/03/what-every-web-developer-must-know-about-url-encoding
     188     */
     189    function test_reserved_characters() {
     190        $url = "http://example.com/:@-._~!$&'()*+,=;:@-._~!$&'()*+,=:@-._~!$&'()*+,==?/?:@-._~!$%27()*+,;=/?:@-._~!$%27()*+,;==#/?:@-._~!$&'()*+,;=";
     191        $this->assertEquals( $url, esc_url_raw( $url ) );
     192    }
     193
     194    /**
    129195     * @ticket 21974
    130196     */
     
    176242     */
    177243    function test_invalid_charaters() {
    178         $this->assertEmpty( esc_url_raw('"^[]<>{}`') );
     244        $this->assertEmpty( esc_url_raw('"^<>{}`') );
    179245    }
    180246
Note: See TracChangeset for help on using the changeset viewer.