From 12e06a10ac46b7216bfbac41bc0015ff7bf49380 Mon Sep 17 00:00:00 2001
From: jrfnl <github_nospam@adviesenzo.nl>
Date: Fri, 30 Sep 2016 22:59:58 +0200
Subject: [PATCH] Add the $component parameter to the wp_parse_url() function.

---
 src/wp-includes/http.php          | 73 +++++++++++++++++++++++++++----------
 tests/phpunit/tests/http/http.php | 77 ++++++++++++++++++++++++++++++++++++++-
 2 files changed, 129 insertions(+), 21 deletions(-)

diff --git a/src/wp-includes/http.php b/src/wp-includes/http.php
index 130eae3..3738b79 100644
--- a/src/wp-includes/http.php
+++ b/src/wp-includes/http.php
@@ -633,35 +633,68 @@ function ms_allowed_http_request_hosts( $is_external, $host ) {
  * when URL parsing failed.
  *
  * @since 4.4.0
- *
- * @param string $url The URL to parse.
- * @return bool|array False on failure; Array of URL components on success;
- *                    See parse_url()'s return values.
+ * @since 4.7.0 The $component parameter was added for parity with PHP's parse_url().
+ *
+ * @param string $url       The URL to parse.
+ * @param int    $component The specific component to retrieve. Use one of the PHP
+ *                          predefined constants to specify which one.
+ *                          Defaults to -1 (= return all parts as an array).
+ *                          @see http://php.net/manual/en/function.parse-url.php
+ * @return mixed False on failure; Array of URL components on success;
+ *               When a specific component has been requested: null if the component doesn't
+ *               exist in the given URL; a sting or - in the case of PHP_URL_PORT - integer
+ *               when it does; See parse_url()'s return values.
  */
-function wp_parse_url( $url ) {
-	$parts = @parse_url( $url );
-	if ( ! $parts ) {
-		// < PHP 5.4.7 compat, trouble with relative paths including a scheme break in the path
+function wp_parse_url( $url, $component = -1 ) {
+	$parts = @parse_url( $url, $component );
+
+	if ( version_compare( PHP_VERSION, '5.4.7', '>=' ) ) {
+		return $parts;
+	}
+
+	if ( false === $parts ) {
+		// < PHP 5.4.7 compat, trouble with relative paths including a scheme break in the path.
 		if ( '/' == $url[0] && false !== strpos( $url, '://' ) ) {
-			// Since we know it's a relative path, prefix with a scheme/host placeholder and try again
-			if ( ! $parts = @parse_url( 'placeholder://placeholder' . $url ) ) {
+			if ( in_array( $component, array( PHP_URL_SCHEME, PHP_URL_HOST ), true ) ) {
+				return null;
+			}
+			// Since we know it's a relative path, prefix with a scheme/host placeholder and try again.
+			if ( ! $parts = @parse_url( 'placeholder://placeholder' . $url, $component ) ) {
 				return $parts;
 			}
-			// Remove the placeholder values
-			unset( $parts['scheme'], $parts['host'] );
+			// Remove the placeholder values.
+			if ( -1 === $component ) {
+				unset( $parts['scheme'], $parts['host'] );
+			}
 		} else {
 			return $parts;
 		}
 	}
 
-	// < PHP 5.4.7 compat, doesn't detect schemeless URL's host field
-	if ( '//' == substr( $url, 0, 2 ) && ! isset( $parts['host'] ) ) {
-		$path_parts = explode( '/', substr( $parts['path'], 2 ), 2 );
-		$parts['host'] = $path_parts[0];
-		if ( isset( $path_parts[1] ) ) {
-			$parts['path'] = '/' . $path_parts[1];
-		} else {
-			unset( $parts['path'] );
+	// < PHP 5.4.7 compat, doesn't detect a schemeless URL's host field.
+	if ( '//' == substr( $url, 0, 2 ) ) {
+		if ( -1 === $component && ! isset( $parts['host'] ) ) {
+			$path_parts = explode( '/', substr( $parts['path'], 2 ), 2 );
+			$parts['host'] = $path_parts[0];
+			if ( isset( $path_parts[1] ) ) {
+				$parts['path'] = '/' . $path_parts[1];
+			} else {
+				unset( $parts['path'] );
+			}
+		} elseif ( PHP_URL_HOST === $component || PHP_URL_PATH === $component ) {
+			$all_parts = @parse_url( $url );
+			if ( ! isset( $all_parts['host'] ) ) {
+				$path_parts = explode( '/', substr( $all_parts['path'], 2 ), 2 );
+				if ( PHP_URL_PATH === $component ) {
+					if ( isset( $path_parts[1] ) ) {
+						$parts = '/' . $path_parts[1];
+					} else {
+						$parts = null;
+					}
+				} elseif ( PHP_URL_HOST === $component ) {
+					$parts = $path_parts[0];
+				}
+			}
 		}
 	}
 
diff --git a/tests/phpunit/tests/http/http.php b/tests/phpunit/tests/http/http.php
index ed0b9ee..607eb47 100644
--- a/tests/phpunit/tests/http/http.php
+++ b/tests/phpunit/tests/http/http.php
@@ -6,6 +6,8 @@
  */
 class Tests_HTTP_HTTP extends WP_UnitTestCase {
 
+	const FULL_TEST_URL = 'http://username:password@host.name:9090/path?arg1=value1&arg2=value2#anchor';
+
 	/**
 	 * @dataProvider make_absolute_url_testcases
 	 */
@@ -78,6 +80,16 @@ class Tests_HTTP_HTTP extends WP_UnitTestCase {
 	function parse_url_testcases() {
 		// 0: The URL, 1: The expected resulting structure
 		return array(
+			array( self::FULL_TEST_URL, array(
+				'scheme'   => 'http',
+				'host'     => 'host.name',
+				'port'     => 9090,
+				'user'     => 'username',
+				'pass'     => 'password',
+				'path'     => '/path',
+				'query'    => 'arg1=value1&arg2=value2',
+				'fragment' => 'anchor',
+			) ),
 			array( 'http://example.com/', array( 'scheme' => 'http', 'host' => 'example.com', 'path' => '/' ) ),
 
 			// < PHP 5.4.7: Schemeless URL
@@ -85,7 +97,7 @@ class Tests_HTTP_HTTP extends WP_UnitTestCase {
 			array( '//example.com/', array( 'host' => 'example.com', 'path' => '/' ) ),
 			array( 'http://example.com//path/', array( 'scheme' => 'http', 'host' => 'example.com', 'path' => '//path/' ) ),
 
-			// < PHP 5.4.7: Scheme seperator in the URL
+			// < PHP 5.4.7: Scheme separator in the URL.
 			array( 'http://example.com/http://example.net/', array( 'scheme' => 'http', 'host' => 'example.com', 'path' => '/http://example.net/' ) ),
 			array( '/path/http://example.net/', array( 'path' => '/path/http://example.net/' ) ),
 
@@ -104,6 +116,69 @@ class Tests_HTTP_HTTP extends WP_UnitTestCase {
 	}
 
 	/**
+	 * @ticket 36356
+     */
+	function test_wp_parse_url_with_default_component() {
+		$actual = wp_parse_url( self::FULL_TEST_URL, -1 );
+		$this->assertEquals( array(
+			'scheme'   => 'http',
+			'host'     => 'host.name',
+			'port'     => 9090,
+			'user'     => 'username',
+			'pass'     => 'password',
+			'path'     => '/path',
+			'query'    => 'arg1=value1&arg2=value2',
+			'fragment' => 'anchor',
+		), $actual );
+	}
+
+	/**
+	 * @ticket 36356
+	 *
+	 * @dataProvider parse_url_component_testcases
+	 */
+	function test_wp_parse_url_with_component( $url, $component, $expected ) {
+		$actual = wp_parse_url( $url, $component );
+		$this->assertSame( $expected, $actual );
+	}
+
+	function parse_url_component_testcases() {
+		// 0: The URL, 1: The requested component, 2: The expected resulting structure.
+		return array(
+			array( self::FULL_TEST_URL, PHP_URL_SCHEME, 'http' ),
+			array( self::FULL_TEST_URL, PHP_URL_USER, 'username' ),
+			array( self::FULL_TEST_URL, PHP_URL_PASS, 'password' ),
+			array( self::FULL_TEST_URL, PHP_URL_HOST, 'host.name' ),
+			array( self::FULL_TEST_URL, PHP_URL_PORT, 9090 ),
+			array( self::FULL_TEST_URL, PHP_URL_PATH, '/path' ),
+			array( self::FULL_TEST_URL, PHP_URL_QUERY, 'arg1=value1&arg2=value2' ),
+			array( self::FULL_TEST_URL, PHP_URL_FRAGMENT, 'anchor' ),
+
+			// < PHP 5.4.7: Schemeless URL.
+			array( '//example.com/path/', PHP_URL_HOST, 'example.com' ),
+			array( '//example.com/path/', PHP_URL_PATH, '/path/' ),
+			array( '//example.com/', PHP_URL_HOST, 'example.com' ),
+			array( '//example.com/', PHP_URL_PATH, '/' ),
+			array( 'http://example.com//path/', PHP_URL_HOST, 'example.com' ),
+			array( 'http://example.com//path/', PHP_URL_PATH, '//path/' ),
+
+			// < PHP 5.4.7: Scheme separator in the URL.
+			array( 'http://example.com/http://example.net/', PHP_URL_HOST, 'example.com' ),
+			array( 'http://example.com/http://example.net/', PHP_URL_PATH, '/http://example.net/' ),
+			array( '/path/http://example.net/', PHP_URL_HOST, null ),
+			array( '/path/http://example.net/', PHP_URL_PATH, '/path/http://example.net/' ),
+
+			// < PHP 5.4.7: IPv6 literals in schemeless URLs are handled incorrectly.
+			array( '//[::FFFF::127.0.0.1]/', PHP_URL_HOST, '[::FFFF::127.0.0.1]' ),
+			array( '//[::FFFF::127.0.0.1]/', PHP_URL_PATH, '/' ),
+
+			// PHP's parse_url() calls this an invalid URL, we handle it as a path.
+			array( '/://example.com/', PHP_URL_PATH, '/://example.com/' ),
+
+		);
+	}
+
+	/**
 	 * @ticket 35426
 	 */
 	public function test_http_response_code_constants() {
-- 
1.9.5.msysgit.0

