reverted: --- b/core/lib/Drupal/Component/Utility/Url.php +++ a/core/lib/Drupal/Component/Utility/Url.php @@ -206,11 +206,10 @@ */ public static function isExternal($path) { $colonpos = strpos($path, ':'); - $scheme_relative = strpos($path, '//') === 0; // Avoid calling stripDangerousProtocols() if there is any // slash (/), hash (#) or question_mark (?) before the colon (:) // occurrence - if any - as this would clearly mean it is not a URL. + return $colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && static::stripDangerousProtocols($path) == $path; - return ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) || $scheme_relative) && static::stripDangerousProtocols($path) == $path; } /** @@ -331,7 +330,7 @@ if ($absolute) { return (bool) preg_match(" /^ # Start at the beginning of the text + (?:ftp|https?|feed):\/\/ # Look for ftp, http, https or feed schemes - (?:ftp:|https?:|feed:)?\/\/ # Look for ftp, http, https, feed or scheme relative schemes (?: # Userinfo (optional) which is typically (?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)* # a username or a username and password (?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@ # combination diff -u b/core/lib/Drupal/Core/Routing/UrlGenerator.php b/core/lib/Drupal/Core/Routing/UrlGenerator.php --- b/core/lib/Drupal/Core/Routing/UrlGenerator.php +++ b/core/lib/Drupal/Core/Routing/UrlGenerator.php @@ -228,9 +228,8 @@ // before any / ? or #. Note: we could use url_is_external($path) here, but // that would require another function call, and performance inside url() is // critical. - $scheme_relative = strpos($path, '//') === 0; $colonpos = strpos($path, ':'); - $options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) || $scheme_relative) && Url::stripDangerousProtocols($path) == $path; + $options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && Url::stripDangerousProtocols($path) == $path); } if (isset($options['fragment']) && $options['fragment'] !== '') { @@ -253,8 +252,9 @@ // contains a ':' before any / ? or #. Note: we could use // \Drupal\Component\Utility\UrlHelper::isExternal($path) here, but that // would require another function call, and performance here is critical. + $scheme_relative = strpos($path, '//') === 0; $colonpos = strpos($path, ':'); - $options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && UrlHelper::stripDangerousProtocols($path) == $path); + $options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) || $scheme_relative) && UrlHelper::stripDangerousProtocols($path) == $path; } if (isset($options['fragment']) && $options['fragment'] !== '') { reverted: --- b/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php +++ a/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php @@ -463,20 +463,6 @@ } /** - * Add a menu link with a scheme-relative url. - */ - public function testMenuSchemeRelative() { - $this->drupalLogin($this->big_user); - - // Make a path with query and fragment on. - $path = '//drupal.org'; - $item = $this->addMenuLink(0, $path); - - $this->drupalGet('admin/structure/menu/item/' . $item['mlid'] . '/edit'); - $this->assertFieldByName('link_path', $path, 'Path is found with scheme-relative URL.'); - } - - /** * Add a menu link using the menu module UI. * * @param integer $plid Parent menu link id. reverted: --- b/core/modules/system/lib/Drupal/system/Tests/Common/UrlTest.php +++ a/core/modules/system/lib/Drupal/system/Tests/Common/UrlTest.php @@ -290,37 +290,4 @@ $result = url($url, array('query' => $query)); $this->assertEqual($url . '&' . http_build_query($query, '', '&'), $result, 'External URL query string can be extended with a custom query string in $options.'); } - - public function testSchemeRelativeUrls() { - $test_url = '//drupal.org'; - - // Verify scheme-relative URL can contain a fragment. - $url = $test_url . '#drupal'; - $result = url($url); - $this->assertEqual($url, $result, 'Scheme-relative URL with fragment works without a fragment in $options.'); - - // Verify fragment can be overidden in an external URL. - $url = $test_url . '#drupal'; - $fragment = $this->randomName(10); - $result = url($url, array('fragment' => $fragment)); - $this->assertEqual($test_url . '#' . $fragment, $result, 'Scheme-relative URL fragment is overidden with a custom fragment in $options.'); - - // Verify external URL can contain a query string. - $url = $test_url . '?drupal=awesome'; - $result = url($url); - $this->assertEqual($url, $result, 'Scheme-relative URL with query string works without a query string in $options.'); - - // Verify external URL can be extended with a query string. - $url = $test_url; - $query = array($this->randomName(5) => $this->randomName(5)); - $result = url($url, array('query' => $query)); - $this->assertEqual($url . '?' . http_build_query($query, '', '&'), $result, 'Scheme-relative URL can be extended with a query string in $options.'); - - // Verify query string can be extended in an external URL. - $url = $test_url . '?drupal=awesome'; - $query = array($this->randomName(5) => $this->randomName(5)); - $result = url($url, array('query' => $query)); - $this->assertEqual($url . '&' . http_build_query($query, '', '&'), $result, 'Scheme-relative URL query string can be extended with a custom query string in $options.'); - } - } reverted: --- b/core/tests/Drupal/Tests/Component/Utility/UrlTest.php +++ a/core/tests/Drupal/Tests/Component/Utility/UrlTest.php @@ -96,7 +96,7 @@ * @dataProvider providerTestValidAbsoluteData */ public function testValidAbsolute($url, $scheme) { + $test_url = $scheme . '://' . $url; - $test_url = $scheme . $url; $valid_url = Url::isValid($test_url, TRUE); $this->assertTrue($valid_url, String::format('@url is a valid URL.', array('@url' => $test_url))); } @@ -124,7 +124,7 @@ * @dataProvider providerTestInvalidAbsolute */ public function testInvalidAbsolute($url, $scheme) { + $test_url = $scheme . '://' . $url; - $test_url = $scheme . $url; $valid_url = Url::isValid($test_url, TRUE); $this->assertFalse($valid_url, String::format('@url is NOT a valid URL.', array('@url' => $test_url))); } @@ -198,7 +198,7 @@ * A list of provider data with schemes. */ protected function dataEnhanceWithScheme(array $urls) { + $url_schemes = array('http', 'https', 'ftp'); - $url_schemes = array('http://', 'https://', 'ftp://', '//'); $data = array(); foreach ($url_schemes as $scheme) { foreach ($urls as $url) { only in patch2: unchanged: --- a/core/lib/Drupal/Component/Utility/UrlHelper.php +++ b/core/lib/Drupal/Component/Utility/UrlHelper.php @@ -214,10 +214,11 @@ public static function encodePath($path) { */ public static function isExternal($path) { $colonpos = strpos($path, ':'); + $scheme_relative = strpos($path, '//') === 0; // Avoid calling stripDangerousProtocols() if there is any // slash (/), hash (#) or question_mark (?) before the colon (:) // occurrence - if any - as this would clearly mean it is not a URL. - return $colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && static::stripDangerousProtocols($path) == $path; + return ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) || $scheme_relative) && static::stripDangerousProtocols($path) == $path; } /** @@ -355,7 +356,7 @@ public static function isValid($url, $absolute = FALSE) { if ($absolute) { return (bool) preg_match(" /^ # Start at the beginning of the text - (?:ftp|https?|feed):\/\/ # Look for ftp, http, https or feed schemes + (?:ftp:|https?:|feed:)?\/\/ # Look for ftp, http, https, feed or scheme relative schemes (?: # Userinfo (optional) which is typically (?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)* # a username or a username and password (?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@ # combination only in patch2: unchanged: --- a/core/modules/menu_ui/src/Tests/MenuTest.php +++ b/core/modules/menu_ui/src/Tests/MenuTest.php @@ -587,6 +587,20 @@ public function testBlockContextualLinks() { } /** + * Add a menu link with a scheme-relative url. + */ + public function testMenuSchemeRelative() { + $this->drupalLogin($this->adminUser); + + // Make a path with query and fragment on. + $path = '//drupal.org'; + $item = $this->addMenuLink(0, $path); + + $this->drupalGet('admin/structure/menu/item/' . $item['mlid'] . '/edit'); + $this->assertFieldByName('link_path', $path, 'Path is found with scheme-relative URL.'); + } + + /** * Adds a menu link using the UI. * * @param string $parent only in patch2: unchanged: --- a/core/modules/system/src/Tests/Common/UrlTest.php +++ b/core/modules/system/src/Tests/Common/UrlTest.php @@ -279,4 +279,37 @@ function testExternalUrls() { $result = Url::fromUri($url, array('query' => $query))->toString(); $this->assertEqual($url . '&' . http_build_query($query, '', '&'), $result); } + + public function testSchemeRelativeUrls() { + $test_url = '//drupal.org'; + + // Verify scheme-relative URL can contain a fragment. + $url = $test_url . '#drupal'; + $result = \Drupal::url($url); + $this->assertEqual($url, $result, 'Scheme-relative URL with fragment works without a fragment in $options.'); + + // Verify fragment can be overidden in an external URL. + $url = $test_url . '#drupal'; + $fragment = $this->randomMachineName(10); + $result = \Drupal::url($url, array('fragment' => $fragment)); + $this->assertEqual($test_url . '#' . $fragment, $result, 'Scheme-relative URL fragment is overidden with a custom fragment in $options.'); + + // Verify external URL can contain a query string. + $url = $test_url . '?drupal=awesome'; + $result = \Drupal::url($url); + $this->assertEqual($url, $result, 'Scheme-relative URL with query string works without a query string in $options.'); + + // Verify external URL can be extended with a query string. + $url = $test_url; + $query = array($this->randomMachineName(5) => $this->randomMachineName(5)); + $result = \Drupal::url($url, array('query' => $query)); + $this->assertEqual($url . '?' . http_build_query($query, '', '&'), $result, 'Scheme-relative URL can be extended with a query string in $options.'); + + // Verify query string can be extended in an external URL. + $url = $test_url . '?drupal=awesome'; + $query = array($this->randomMachineName(5) => $this->randomMachineName(5)); + $result = \Drupal::url($url, array('query' => $query)); + $this->assertEqual($url . '&' . http_build_query($query, '', '&'), $result, 'Scheme-relative URL query string can be extended with a custom query string in $options.'); + } + } only in patch2: unchanged: --- a/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php @@ -92,7 +92,7 @@ public function providerTestValidAbsoluteData() { * The scheme to test. */ public function testValidAbsolute($url, $scheme) { - $test_url = $scheme . '://' . $url; + $test_url = $scheme . $url; $valid_url = UrlHelper::isValid($test_url, TRUE); $this->assertTrue($valid_url, String::format('@url is a valid URL.', array('@url' => $test_url))); } @@ -123,7 +123,7 @@ public function providerTestInvalidAbsolute() { * The scheme to test. */ public function testInvalidAbsolute($url, $scheme) { - $test_url = $scheme . '://' . $url; + $test_url = $scheme . $url; $valid_url = UrlHelper::isValid($test_url, TRUE); $this->assertFalse($valid_url, String::format('@url is NOT a valid URL.', array('@url' => $test_url))); } @@ -445,7 +445,7 @@ public static function providerTestStripDangerousProtocols() { * A list of provider data with schemes. */ protected function dataEnhanceWithScheme(array $urls) { - $url_schemes = array('http', 'https', 'ftp'); + $url_schemes = array('http://', 'https://', 'ftp://', '//'); $data = array(); foreach ($url_schemes as $scheme) { foreach ($urls as $url) {