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
This commit is contained in:
John Blackbourn 2015-10-07 23:38:22 +00:00
parent f9f92b223d
commit 4046919a11
2 changed files with 82 additions and 11 deletions

View File

@ -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] . "<a href=\"$dest\" rel=\"nofollow\">$dest</a>$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( "'", '&#039;', $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 {

View File

@ -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&#038;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&#038;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&#038;foo%5Bbar%5D=baz', esc_url( 'http://example.com/?baz=bar&foo[bar]=baz' ) );
$this->assertEquals( 'http://example.com/?baz=bar&#038;foo%5Bbar%5D=baz', esc_url( 'http://example.com/?baz=bar&#038;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('"^<>{}`') );
}
}