Make WordPress Core

Changeset 34674


Ignore:
Timestamp:
09/29/2015 12:40:30 AM (11 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.