Index: src/wp-includes/formatting.php
===================================================================
--- src/wp-includes/formatting.php	(revision 34670)
+++ src/wp-includes/formatting.php	(working copy)
@@ -2077,15 +2077,17 @@
 	$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";
 }
 
@@ -3278,6 +3280,8 @@
  * (the default behaviour) ampersands are also replaced. The 'clean_url' filter
  * is applied to the returned cleaned URL.
  *
+ * See RFC3986
+ *
  * @since 2.8.0
  *
  * @param string $url       The URL to be cleaned.
@@ -3293,7 +3297,7 @@
 		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;
@@ -3306,7 +3310,7 @@
 
 	$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( '/', '#', '?' ) ) &&
@@ -3320,6 +3324,43 @@
 		$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 {
Index: tests/phpunit/tests/formatting/EscUrl.php
===================================================================
--- tests/phpunit/tests/formatting/EscUrl.php	(revision 34670)
+++ tests/phpunit/tests/formatting/EscUrl.php	(working copy)
@@ -40,15 +40,41 @@
 	}
 
 	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 ) );
-
-		$this->assertEquals( 'https://user:password@host.example.com:1234/path;p=1?q=2&#038;r=3#fragment', esc_url( $url ) );
+		$url = 'https://user:pass@host.example.com:1234/path;p=1?query=2&r[]=3#fragment';
 
-		$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_all_url_parts_ipv6() {
+		$url = 'https://user:pass@[::FFFF::127.0.0.1]:1234/path;p=1?query=2&r[]=3#fragment';
+
+		$this->assertEquals( array(
+			'scheme'   => 'https',
+			'host'     => '[::FFFF::127.0.0.1]',
+			'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@[::FFFF::127.0.0.1]:1234/path;p=1?query=2&r%5B%5D=3#fragment', esc_url_raw( $url ) );
+		$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 ) );
 	}
 
 	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' ) );
@@ -126,6 +152,43 @@
 	}
 
 	/**
+	 * @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' ) );
+	}
+
+	/**
+	 * @ticket 16859
+	 */
+	function test_ipv6_hosts() {
+		$this->assertEquals( '//[::127.0.0.1]', esc_url( '//[::127.0.0.1]' ) );
+		$this->assertEquals( 'http://[::FFFF::127.0.0.1]', esc_url( 'http://[::FFFF::127.0.0.1]' ) );
+		$this->assertEquals( 'http://[::127.0.0.1]', esc_url( 'http://[::127.0.0.1]' ) );
+		$this->assertEquals( 'http://[::DEAD:BEEF:DEAD:BEEF:DEAD:BEEF:DEAD:BEEF]', esc_url( 'http://[::DEAD:BEEF:DEAD:BEEF:DEAD:BEEF:DEAD:BEEF]' ) );
+
+		// IPv6 with square brackets in the query? Why not.
+		$this->assertEquals( '//[::FFFF::127.0.0.1]/?foo%5Bbar%5D=baz', esc_url( '//[::FFFF::127.0.0.1]/?foo[bar]=baz' ) );
+		$this->assertEquals( 'http://[::FFFF::127.0.0.1]/?foo%5Bbar%5D=baz', esc_url( 'http://[::FFFF::127.0.0.1]/?foo[bar]=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
 	 */
 	function test_protocol_relative_with_colon() {
@@ -175,7 +238,7 @@
 	 * @ticket 28015
 	 */
 	function test_invalid_charaters() {
-		$this->assertEmpty( esc_url_raw('"^[]<>{}`') );
+		$this->assertEmpty( esc_url_raw('"^<>{}`') );
 	}
 
 }
