From 3fb9c3dd29252e3b023f0068fd64e25caffd6843 Mon Sep 17 00:00:00 2001
From: Claudiu Cristea <7931-claudiucristea@users.noreply.drupalcode.org>
Date: Wed, 12 Mar 2025 19:33:51 +0000
Subject: [PATCH 1/3] Test coverage

---
 .../Tests/Component/Utility/UrlHelperTest.php | 24 +++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php b/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php
index 158e626c8213..f7372b438694 100644
--- a/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php
+++ b/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php
@@ -386,6 +386,30 @@ public static function providerTestParse() {
           'fragment' => 'footer',
         ],
       ],
+      'Absolute URL with URL as fragment' => [
+        'http://example.com/my/path#http://example.com',
+        [
+          'fragment' => 'http://example.com',
+          'path' => 'http://example.com/my/path',
+          'query' => [],
+        ],
+      ],
+      'Relative URL with URL as fragment' => [
+        '/my/path#http://example.com',
+        [
+          'fragment' => 'http://example.com',
+          'path' => 'http://example.com/my/path',
+          'query' => [],
+        ],
+      ],
+      'URL as fragment' => [
+        '#http://example.com',
+        [
+          'fragment' => 'http://example.com',
+          'path' => 'http://example.com/my/path',
+          'query' => [],
+        ],
+      ],
     ];
   }
 
-- 
GitLab


From 6addc663e775cf5ab052c002dff0bb2df9de6148 Mon Sep 17 00:00:00 2001
From: Claudiu Cristea <7931-claudiucristea@users.noreply.drupalcode.org>
Date: Wed, 12 Mar 2025 19:37:08 +0000
Subject: [PATCH 2/3] Fix expectancy

---
 core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php b/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php
index f7372b438694..f22d6ac3118a 100644
--- a/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php
+++ b/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php
@@ -398,7 +398,7 @@ public static function providerTestParse() {
         '/my/path#http://example.com',
         [
           'fragment' => 'http://example.com',
-          'path' => 'http://example.com/my/path',
+          'path' => '/my/path',
           'query' => [],
         ],
       ],
@@ -406,7 +406,7 @@ public static function providerTestParse() {
         '#http://example.com',
         [
           'fragment' => 'http://example.com',
-          'path' => 'http://example.com/my/path',
+          'path' => '',
           'query' => [],
         ],
       ],
-- 
GitLab


From 8fa99bb13e5fdf120c8352460117e8333e23f160 Mon Sep 17 00:00:00 2001
From: Claudiu Cristea <7931-claudiucristea@users.noreply.drupalcode.org>
Date: Wed, 12 Mar 2025 19:52:27 +0000
Subject: [PATCH 3/3] The fix

---
 core/lib/Drupal/Component/Utility/UrlHelper.php | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/core/lib/Drupal/Component/Utility/UrlHelper.php b/core/lib/Drupal/Component/Utility/UrlHelper.php
index 9619dcf07031..27dfc945b18f 100644
--- a/core/lib/Drupal/Component/Utility/UrlHelper.php
+++ b/core/lib/Drupal/Component/Utility/UrlHelper.php
@@ -187,13 +187,18 @@ public static function parse($url) {
     ];
 
     // External URLs: not using parse_url() here, so we do not have to rebuild
-    // the scheme, host, and path without having any use for it.
-    // The URL is considered external if it contains the '://' delimiter. Since
-    // a URL can also be passed as a query argument, we check if this delimiter
-    // appears in front of the '?' query argument delimiter.
+    // the scheme, host, and path without having any use for it. The URL is
+    // considered external if it contains the '://' delimiter. Since a URL can
+    // also be passed as a query or fragment argument, check if this delimiter
+    // appears in front of the '?' and '#' query/fragment argument delimiters.
     $scheme_delimiter_position = strpos($url, '://');
     $query_delimiter_position = strpos($url, '?');
-    if ($scheme_delimiter_position !== FALSE && ($query_delimiter_position === FALSE || $scheme_delimiter_position < $query_delimiter_position)) {
+    $fragment_delimiter_position = strpos($url, '#');
+    if (
+      $scheme_delimiter_position !== FALSE &&
+      ($query_delimiter_position === FALSE || $scheme_delimiter_position < $query_delimiter_position) &&
+      ($fragment_delimiter_position === FALSE || $scheme_delimiter_position < $fragment_delimiter_position)
+    ) {
       // Split off the fragment, if any.
       if (str_contains($url, '#')) {
         [$url, $options['fragment']] = explode('#', $url, 2);
-- 
GitLab