Problem/Motivation

Examples:

  • template_preprocess_file_link()
  • \Drupal\file\Plugin\Field\FieldFormatter\BaseFieldFileFormatterBase::viewElements()
  • \Drupal\file\Plugin\Field\FieldFormatter\FileUriFormatter::viewValue()

These all generate links (<a href="…"></a>) using LinkGenerator/\Drupal\Core\Link, which in turn require \Drupal\Core\Url objects.

Those \Drupal\Core\Url objects require a URL scheme. Which means root-relative (file) URLs cannot be passed to them, which means we must generate absolute file URLs, which means trouble as soon as you encounter sites available over both HTTP and HTTPS.

Proposed resolution

Detect root-relative URLs, automatically use the base: scheme. This is then similar to what \Drupal\Core\Url::fromUri() already does for protocol-relative URLs.

Remaining tasks

None.

User interface changes

None.

API changes

No changes; one addition: Url::fromUri() no longer considers /cat.png invalid (i.e. as using an invalid URI scheme); instead it detects this as a root-relative URL and hence uses the base: scheme.

Data model changes

None.

Issue fork drupal-2646744

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

Wim Leers created an issue. See original summary.

wim leers’s picture

Issue tags: +https, +HTTP/2
dawehner’s picture

, which means trouble as soon as you encounter sites available over both HTTP and HTTPS.

Isn't that the point of protocol relative URLs? //foo/bar/baz.js

wim leers’s picture

  • Protocol-relative URLs are handy when a site is accessible via a single domain but multiple protocols.
  • Root-relative URLs are handy when a site is accessible via multiple domains. (And either a single protocol or multiple protocols.)

But protocol-relative URLs are also not supported by the Url class.

dawehner’s picture

There is #2573635: Url::fromUri() should accept protocol-relative URLs for protocol relative URLs.
In that issue I made the point that the URL class should implement everything which is possible, but some people disagree with it.

xjm’s picture

Component: base system » routing system

We usually treat the URL generation as a part of routing, so moving there. (Maybe the component should be "routing and URL generation system".)

wim leers’s picture

Maybe the component should be "routing and URL generation system".

+1

mac_weber’s picture

@Wim Leers

But protocol-relative URLs are also not supported by the Url class.

There is an open issue for protocol-relative: #1783278: Scheme-relative URL rejected by validation

effulgentsia’s picture

Discussed this with @catch, @alexpott, @xjm, and @Cottser, and we agreed this is Major, because it appears to be a regression from Drupal 7, which would impede the porting of sites that rely on it to Drupal 8.

mac_weber’s picture

This is very related to this issue and others that need an improvement at the Url class: #2691099: Improve external URL validation in many ways

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

oleksiy’s picture

Status: Active » Needs review
StatusFileSize
new2.17 KB

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

andypost’s picture

Version: 8.5.x-dev » 8.6.x-dev
Status: Needs review » Needs work

needs work for tests

oleksiy’s picture

There is the available test in the patch. Can you say what's wrong there, please?

andypost’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
StatusFileSize
new1.57 KB

@Oleksiy it needs "test-only" patch to prove coverage

Status: Needs review » Needs work

The last submitted patch, 19: url_root_relative-2646744-15-test-only.patch, failed testing. View results

zymbian’s picture

StatusFileSize
new461 bytes

Providing interdiff for patch #15 and #19 .

andypost’s picture

Status: Needs work » Reviewed & tested by the community

@zymbian for testonly patch no interdiff is needed, it is the same patch with tests except bug fix - see 13) in https://www.drupal.org/contributor-tasks/write-tests

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs issue summary update
  1. +++ b/core/lib/Drupal/Core/Url.php
    @@ -277,6 +277,10 @@ public static function fromUri($uri, $options = []) {
    +    // We support root-relative URLs.
    

    The word We is unnecessary.

  2. +++ b/core/tests/Drupal/Tests/Core/UnroutedUrlTest.php
    @@ -84,6 +84,8 @@ public function providerFromUri() {
    +      // A root-relative URL.
    +      ['/robots.txt', FALSE],
    
    @@ -115,7 +117,6 @@ public function providerFromInvalidUri() {
    -      ['/test'],
    

    I wonder if the change in behaviour will have any negative impacts. I.e. times when we want the invalid URL exception for validation purposes.

  3. The issue summary has lots of TBD and therefore needs updating.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

berdir’s picture

Coming here from the @todo in \Drupal\file\Plugin\Field\FieldFormatter\BaseFieldFileFormatterBase::viewElements().

The thing is that the @todo there and the issue title at least here are wrong. As the patch provides, root-relative paths work just fine, you simply have to prefix it with base:.

I'm not sure we need to fix anything here, it simply means that a caller to fromUri() needs to prefix the URL with base.

wim leers’s picture

Issue summary: View changes
Status: Needs work » Needs review
Issue tags: -Needs issue summary update
StatusFileSize
new775 bytes
new1.55 KB
new2.57 KB

Looks like that's what #15 was already doing.

Addressing #23:

  1. That's just following the pattern above for protocol-relative URLs. Fixed both.
  2. If that were the case, some tests would be failing? That's what the test-only patch in #19 shows.
  3. Done.

The last submitted patch, 26: 2646744-26-test_only.patch, failed testing. View results

wim leers’s picture

StatusFileSize
new6.72 KB
new9.27 KB

We should also fix the @todos that were referring to this.

berdir’s picture

+++ b/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php
@@ -127,11 +127,9 @@ public function _testImageFieldFormatters($scheme) {
     $this->assertCacheTag($file->getCacheTags()[0]);
-    // @todo Remove in https://www.drupal.org/node/2646744.
-    $this->assertCacheContext('url.site');

does it make sense to assert that we don't have any cache contexts explicitly?

Looks like a really nice cleanup, will wait with RTBC until #2402533: Provide File::createFileUrl() as a replacement for the deprecated File:url() implementation is committed as this will need quite a reroll then, should then however also be simpler as we can just remove the FALSE argument in many of these cases.

wim leers’s picture

StatusFileSize
new1.12 KB
new9.73 KB

That uncovered some failures. For UnroutedUrlAssembler::assemble() to work, we need to transform the root-relative URL to a base: URI.

The last submitted patch, 28: 2646744-28.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 30: 2646744-29.patch, failed testing. View results

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new2.91 KB
new12.58 KB

#2402533: Provide File::createFileUrl() as a replacement for the deprecated File:url() implementation just landed! But first, making this green again. ImageFieldDisplayTest passes for me locally though… So I'll stick to "mostly" green.

wim leers’s picture

The last submitted patch, 33: 2646744-33.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 34: 2646744-34.patch, failed testing. View results

berdir’s picture

Status: Needs work » Needs review
StatusFileSize
new13.4 KB
new1.9 KB

Hm, so turns out it doesn't actually work yet, not if you have a installed Drupal in a subfolder. The problem is that file_transform_relative() already includes (or actually, does not remove) the base path, and then if you run that test with against http://localhost/d8, you end up with /d8/d8 as the path.

This fixes the test but is obviously very ugly. Not quite sure what to do about that. This behavior is hardcoded in \Drupal\Core\Utility\UnroutedUrlAssembler::buildLocalUrl(), we have nothing to control that.

Good thing our tests are still running in a sub-folder..

Also fixed ComputedFileUrlTest, that could now actually be a unit test, wasn't sure about converting that here.

dawehner’s picture

  1. +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php
    @@ -200,16 +200,8 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
         foreach ($files as $delta => $file) {
    ...
           if (isset($link_file)) {
    ...
    +        $url = Url::fromUri(mb_substr($file->createFileUrl(), mb_strlen(\Drupal::request()->getBasePath())));
           }
    

    It feels like creating a helper method would helpful. Maybe something like Url::fromUri('relative_drupal://') or so? What do you think about that, would that be worth moving to a separate issue?

  2. +++ b/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php
    diff --git a/core/tests/Drupal/Tests/Core/UnroutedUrlTest.php b/core/tests/Drupal/Tests/Core/UnroutedUrlTest.php
    index fb4b84cc93..777b79b74c 100644
    
    index fb4b84cc93..777b79b74c 100644
    --- a/core/tests/Drupal/Tests/Core/UnroutedUrlTest.php
    
    --- a/core/tests/Drupal/Tests/Core/UnroutedUrlTest.php
    +++ b/core/tests/Drupal/Tests/Core/UnroutedUrlTest.php
    
    +++ b/core/tests/Drupal/Tests/Core/UnroutedUrlTest.php
    +++ b/core/tests/Drupal/Tests/Core/UnroutedUrlTest.php
    @@ -83,6 +83,8 @@ public function providerFromUri() {
    
    @@ -83,6 +83,8 @@ public function providerFromUri() {
           ['https://www.drupal.org', TRUE],
           // A protocol-relative URL.
           ['//www.drupal.org', TRUE],
    +      // A root-relative URL.
    +      ['/robots.txt', FALSE, 'base:robots.txt'],
           // An internal, unrouted, base-relative URI.
           ['base:robots.txt', FALSE],
           // Base-relative URIs with special characters.
    @@ -114,7 +116,6 @@ public function providerFromInvalidUri() {
    
    @@ -114,7 +116,6 @@ public function providerFromInvalidUri() {
         return [
           // Schemeless paths.
           ['test'],
    -      ['/test'],
           // Schemeless path with a query string.
           ['foo?bar'],
    

    Kudos for adding tests in multiple places!

dawehner’s picture

+++ b/core/lib/Drupal/Core/Url.php
@@ -273,10 +273,15 @@ public static function fromUri($uri, $options = []) {
+    // Support root-relative URLs.
+    elseif (strpos($uri, '/') === 0) {
+      $uri_parts['scheme'] = 'base';
+      $uri = 'base:' . substr($uri, 1);
+    }

It fascinates me that there is already documentation for this case.

wim leers’s picture

#37

The problem is that file_transform_relative() already includes (or actually, does not remove) the base path

D'OH OF COURSE 😅

My bad. (But I'm glad I updated existing usages, otherwise this might've gotten committed without us realizing this…)

I wonder if a file_url_transform_base_url() method that returns a \Drupal\Core\Url object with a base: URI is warranted here, which does that stripping of the base path if any?

#39: not sure what you mean :P

berdir’s picture

I was also thinking about another function. But we're trying to deprecate the transform functions in #2669074: Convert file_create_url() & file_url_transform_relative() to service, deprecate it in favor of a new service that actually has the transform-relative built-in in the create URL method, so I' not sure if we should have a new method there (different return values based on query arguments are ugly, we already had to add too much of that for render cacheability stuff), or on Url or what :)

I paused on the related issue to wait for this one to be finished, unsure which order makes sense now.

The problem is that I'm not sure how many other cases are affected, it's a pretty fundamental problem and we only had very few test fails and only noticed it because testbot still runs with a subdirectory for history reasons.. more things could be broken. In fact, I've been advocating to use this API in other issues, so I expect that a considerable amount of code out there is using it incorrectly already :-/

wim leers’s picture

Yeah this is … tough.

I wonder if that leads us to a very different conclusion: that file_create_url() returning plain strings is the problem, that it should return Url objects instead. Url is new in Drupal 8. file_create_url() was written in a time when we just had stream wrappers and strings-as-URLs.

I'm sure this would be nontrivial to do.

But … just imagine how nice it must be to always deal with a Url object. By default, this would result in Url objects with base: URIs. With the https://www.drupal.org/project/cdn module installed, the file URL alteration would make it result in https:// or protocol-relative URIs. Either way, the return value of file_create_url() (and/or its successor) would be a Url object.

I think that most of the pain we're observing is due to us first getting a file-URL-as-a-string and then wanting to convert it to file-URl-as-a-Url-value-object. If we can avoid that conversion step, the pain also disappears. We also gain consistency in DX.

Of course, this would be a quite big undertaking. Thoughts?

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

wim leers’s picture

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

berdir’s picture

Priority: Major » Normal
Status: Needs review » Active

#2669074: Convert file_create_url() & file_url_transform_relative() to service, deprecate it is now in, with a method to return Url objects, and I believe it removed all those @todos from the code base.

The issue here is not yet fixed, but we now do have an API that can generate correct Url objects for files, so I'm downgrading this to normal for now unless we have a use case that doesn't work yet.

We still have work to do to modernize the underlying API's, so that we don't have to convert back and forth between the getExternalUrl() method of stream wrappers, so there _might_ still be specific issues, but they should hopefully be far less common now.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

rokzabukovec’s picture

StatusFileSize
new7.82 KB

Rerolled patch for Drupal 9.3

berdir’s picture

Per my comment above, you shoudn't need this patch anymore, you are mostly just reverting back to wrong/deprecated code. If you use this patch then that might also explain why you are getting errors.

rokzabukovec’s picture

I updated the core to 9.3.2 and this issue came up. After updating the node where I attached some file to it then I saved it and when trying to go to that node the Invalid URL error WSOD came up. This patch is solving the issue. Why? I don't know, but it does.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

andypost’s picture

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

jennakoo made their first commit to this issue’s fork.

jennakoo’s picture

Version: 11.x-dev » 10.1.x-dev
Status: Active » Needs review
Issue tags: -
StatusFileSize
new6.8 KB

Rerolled the patch for Drupal 10.1

andypost’s picture

Version: 10.1.x-dev » 11.x-dev
smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs issue summary update

Think this needs an issue summary update, reading comment 51-53 @berdir makes it sound like this patch shouldn't be needed anymore.

rajab natshah’s picture

Version: 11.x-dev » main