From 4046919a117b1c4cb645f6ff2888486c7267b51f Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 7 Oct 2015 23:38:22 +0000 Subject: [PATCH] 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 tests. Fixes #16859 git-svn-id: https://develop.svn.wordpress.org/trunk@34920 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/formatting.php | 49 ++++++++++++++++++++--- tests/phpunit/tests/formatting/EscUrl.php | 44 +++++++++++++++++--- 2 files changed, 82 insertions(+), 11 deletions(-) diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php index 3e6217d098..b3f18eee38 100644 --- a/src/wp-includes/formatting.php +++ b/src/wp-includes/formatting.php @@ -2137,15 +2137,17 @@ function _make_web_ftp_clickable_cb( $matches ) { $ret = ''; $dest = $matches[2]; $dest = 'http://' . $dest; - $dest = esc_url($dest); - if ( empty($dest) ) - return $matches[0]; // removed trailing [.,;:)] from URL if ( in_array( substr($dest, -1), array('.', ',', ';', ':', ')') ) === true ) { $ret = substr($dest, -1); $dest = substr($dest, 0, strlen($dest)-1); } + + $dest = esc_url($dest); + if ( empty($dest) ) + return $matches[0]; + return $matches[1] . "$dest$ret"; } @@ -3352,7 +3354,7 @@ function esc_url( $url, $protocols = null, $_context = 'display' ) { return $url; $url = str_replace( ' ', '%20', $url ); - $url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\\x80-\\xff]|i', '', $url); + $url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\[\]\\x80-\\xff]|i', '', $url); if ( '' === $url ) { return $url; @@ -3365,7 +3367,7 @@ function esc_url( $url, $protocols = null, $_context = 'display' ) { $url = str_replace(';//', '://', $url); /* If the URL doesn't appear to contain a scheme, we - * presume it needs http:// appended (unless a relative + * presume it needs http:// prepended (unless a relative * link starting with /, # or ? or a php file). */ if ( strpos($url, ':') === false && ! in_array( $url[0], array( '/', '#', '?' ) ) && @@ -3379,6 +3381,43 @@ function esc_url( $url, $protocols = null, $_context = 'display' ) { $url = str_replace( "'", ''', $url ); } + if ( ( false !== strpos( $url, '[' ) ) || ( false !== strpos( $url, ']' ) ) ) { + + $parsed = parse_url( $url ); + $front = ''; + + if ( isset( $parsed['scheme'] ) ) { + $front .= $parsed['scheme'] . '://'; + } elseif ( '/' === $url[0] ) { + $front .= '//'; + } + + if ( isset( $parsed['user'] ) ) { + $front .= $parsed['user']; + } + + if ( isset( $parsed['pass'] ) ) { + $front .= ':' . $parsed['pass']; + } + + if ( isset( $parsed['user'] ) || isset( $parsed['pass'] ) ) { + $front .= '@'; + } + + if ( isset( $parsed['host'] ) ) { + $front .= $parsed['host']; + } + + if ( isset( $parsed['port'] ) ) { + $front .= ':' . $parsed['port']; + } + + $end_dirty = str_replace( $front, '', $url ); + $end_clean = str_replace( array( '[', ']' ), array( '%5B', '%5D' ), $end_dirty ); + $url = str_replace( $end_dirty, $end_clean, $url ); + + } + if ( '/' === $url[0] ) { $good_protocol_url = $url; } else { diff --git a/tests/phpunit/tests/formatting/EscUrl.php b/tests/phpunit/tests/formatting/EscUrl.php index 1289fd7c76..b77fbc3b21 100644 --- a/tests/phpunit/tests/formatting/EscUrl.php +++ b/tests/phpunit/tests/formatting/EscUrl.php @@ -40,15 +40,24 @@ class Tests_Formatting_EscUrl extends WP_UnitTestCase { } function test_all_url_parts() { - $url = 'https://user:password@host.example.com:1234/path;p=1?q=2&r=3#fragment'; - $this->assertEquals( $url, esc_url_raw( $url ) ); + $url = 'https://user:pass@host.example.com:1234/path;p=1?query=2&r[]=3#fragment'; - $this->assertEquals( 'https://user:password@host.example.com:1234/path;p=1?q=2&r=3#fragment', esc_url( $url ) ); - - $this->assertEquals( 'http://example.com?foo', esc_url( 'http://example.com?foo' ) ); + $this->assertEquals( array( + 'scheme' => 'https', + 'host' => 'host.example.com', + 'port' => 1234, + 'user' => 'user', + 'pass' => 'pass', + 'path' => '/path;p=1', + 'query' => 'query=2&r[]=3', + 'fragment' => 'fragment', + ), parse_url( $url ) ); + $this->assertEquals( 'https://user:pass@host.example.com:1234/path;p=1?query=2&r%5B%5D=3#fragment', esc_url_raw( $url ) ); + $this->assertEquals( 'https://user:pass@host.example.com:1234/path;p=1?query=2&r%5B%5D=3#fragment', esc_url( $url ) ); } function test_bare() { + $this->assertEquals( 'http://example.com?foo', esc_url( 'example.com?foo' ) ); $this->assertEquals( 'http://example.com', esc_url( 'example.com' ) ); $this->assertEquals( 'http://localhost', esc_url( 'localhost' ) ); $this->assertEquals( 'http://example.com/foo', esc_url( 'example.com/foo' ) ); @@ -125,6 +134,29 @@ class Tests_Formatting_EscUrl extends WP_UnitTestCase { $this->assertEquals( 'feed:http://wordpress.org/feed/', esc_url( 'feed:http://wordpress.org/feed/' ) ); } + /** + * @ticket 16859 + */ + function test_square_brackets() { + $this->assertEquals( '/example.php?one%5B%5D=two', esc_url( '/example.php?one[]=two' ) ); + $this->assertEquals( '?foo%5Bbar%5D=baz', esc_url( '?foo[bar]=baz' ) ); + $this->assertEquals( '//example.com/?foo%5Bbar%5D=baz', esc_url( '//example.com/?foo[bar]=baz' ) ); + $this->assertEquals( 'http://example.com/?foo%5Bbar%5D=baz', esc_url( 'example.com/?foo[bar]=baz' ) ); + $this->assertEquals( 'http://localhost?foo%5Bbar%5D=baz', esc_url( 'localhost?foo[bar]=baz' ) ); + $this->assertEquals( 'http://example.com/?foo%5Bbar%5D=baz', esc_url( 'http://example.com/?foo[bar]=baz' ) ); + $this->assertEquals( 'http://example.com/?foo%5Bbar%5D=baz', esc_url( 'http://example.com/?foo%5Bbar%5D=baz' ) ); + $this->assertEquals( 'http://example.com/?baz=bar&foo%5Bbar%5D=baz', esc_url( 'http://example.com/?baz=bar&foo[bar]=baz' ) ); + $this->assertEquals( 'http://example.com/?baz=bar&foo%5Bbar%5D=baz', esc_url( 'http://example.com/?baz=bar&foo%5Bbar%5D=baz' ) ); + } + + /** + * Courtesy of http://blog.lunatech.com/2009/02/03/what-every-web-developer-must-know-about-url-encoding + */ + function test_reserved_characters() { + $url = "http://example.com/:@-._~!$&'()*+,=;:@-._~!$&'()*+,=:@-._~!$&'()*+,==?/?:@-._~!$%27()*+,;=/?:@-._~!$%27()*+,;==#/?:@-._~!$&'()*+,;="; + $this->assertEquals( $url, esc_url_raw( $url ) ); + } + /** * @ticket 21974 */ @@ -175,7 +207,7 @@ EOT; * @ticket 28015 */ function test_invalid_charaters() { - $this->assertEmpty( esc_url_raw('"^[]<>{}`') ); + $this->assertEmpty( esc_url_raw('"^<>{}`') ); } }