Child of #2417567: Rename user-path: scheme to internal:

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Task; there is no functional bug.
Prioritized changes The main goal of this issue is improving DX. The issue is a prioritized change because it is a followup from critical and major work in #2407505: [meta] Finalize the menu links (and other user-entered paths) system.
Disruption API addition and (in itself) BC-compatible. Some minor disruption as this refines what is considered "best practice" for handling paths from user input.

Comments

xjm’s picture

Updating the @todos and moving some documentation from the protected method.

dawehner’s picture

+++ b/core/tests/Drupal/Tests/Core/UrlTest.php
@@ -171,6 +171,79 @@ public function testFromRouteFront() {
+  public function testFromUserInput($path) {
+    $url = Url::fromUserInput($path);
+
+    $this->assertInstanceOf('Drupal\Core\Url', $url);
+  }

Is that really the only thing we can assert? For example fragment/options extracting could be tested.

xjm’s picture

Good idea; I'll look into that too.

mpdonadio’s picture

Assigned: xjm » Unassigned
StatusFileSize
new17.71 KB
new567 bytes

Sorry @xjm, the 2/18 deadline on the parent is looming. Few additional assertions.

mpdonadio’s picture

StatusFileSize
new18.43 KB
new1.37 KB

Assert all the things! Not sure why ::assetEmpty() didn't work here.

effulgentsia’s picture

StatusFileSize
new19.43 KB
new3.66 KB

Updated the docs to not use the word "path" for strings that are more than just paths. Also added a few more exceptions to make the implementation match the docs.

Status: Needs review » Needs work

The last submitted patch, 6: add_a-2426181-6.patch, failed testing.

effulgentsia’s picture

Status: Needs work » Needs review
StatusFileSize
new19.43 KB
new650 bytes

Oh, PHP and the NULL/FALSE/empty syndrome.

Status: Needs review » Needs work

The last submitted patch, 8: add_a-2426181-8.patch, failed testing.

effulgentsia’s picture

  1. +++ b/core/modules/views/src/Plugin/views/row/RssFields.php
    @@ -144,7 +144,9 @@ public function render($row) {
    -    $item->link = Url::fromUri('user-path:/' . $this->getField($row_index, $this->options['link_field']))->setAbsolute()->toString();
    +    // @todo Is this actually user input? If so, Views should expect a leading
    +    // slash (followup).
    +    $item->link = Url::fromUserInput('/' . $this->getField($row_index, $this->options['link_field']))->setAbsolute()->toString();
    

    Yes, since this is the value of a field, we need to treat it as user input, so we can remove the question from the @todo.

  2. +++ b/core/modules/views/src/Plugin/views/row/RssFields.php
    @@ -158,7 +160,9 @@ public function render($row) {
         if ($this->options['guid_field_options']['guid_field_is_permalink']) {
           $guid_is_permalink_string = 'true';
    -      $item_guid = Url::fromUri('user-path:/' . $item_guid)->setAbsolute()->toString();
    +    // @todo Is this actually user input? If so, Views should expect a leading
    +    // slash (followup).
    +      $item_guid = Url::fromUserInput('/' . $item_guid)->setAbsolute()->toString();
         }
    

    AFAICT, we have no test coverage for guid_field_is_permalink being set to true. I'm not clear on what the use case for it is. When set to true, is it still expected to be a guid, or do we need to allow for it to be a user-entered path? If it's still expected to be a guid, should we assume that all guids are system generated and therefore NOT user entered / modified? In which case, we might want to leave this one as not using the fromUserInput() wrapper and open a dedicated followup issue to figure out and document these questions/expectations.

effulgentsia’s picture

Status: Needs work » Needs review
StatusFileSize
new19.17 KB
new1.05 KB

The test failures in #8 are due to me having added an exception for protocol-relative user input in #5, but us having pre-existing bugs in HEAD that incorrectly use that. Fixing those bugs is out of scope for this issue, so removing that check.

hussainweb’s picture

Status: Needs review » Reviewed & tested by the community

I checked through the patch and the code and found all invocations to fromUri are changed to fromUserInput wherever user-path was used. I think everything else looks good too. All the todos should be handled in a follow-up (including the todo in the doc for fromUserInput method.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

an @todo without an corresponding issue on d.o is like a scream in space

All the @todo should be accompanied d.o link

xjm’s picture

Assigned: Unassigned » xjm

Rerolling to add the link to the existing followup issue, move the documentation from the protected method, and use the data providers added in #2423579: Url::fromUrl('user-path:/') should throw an exception when a path component without a slash is given.

  1. +++ b/core/lib/Drupal/Core/Url.php
    @@ -190,6 +191,48 @@ public static function fromRouteMatch(RouteMatchInterface $route_match) {
    +   *   A non-empty string from user input that conforms to the syntax of a
    +   *   @link https://tools.ietf.org/html/rfc3986#section-4.2 relative URI reference @endlink
    +   *   where the relative part is of type @link https://tools.ietf.org/html/rfc3986#section-3.3 path-abempty @endlink.
    +   *   In other words, the first character must be a '/', '?', or '#'.
    

    Not sure about putting all this in there. It's not just about conforming to blah blah RFC blah, it's also about supporting the user interaction pattern of these characters indicating the user wants to enter a path instead of autocompleting a title.

  2. +++ b/core/lib/Drupal/Core/Url.php
    @@ -190,6 +191,48 @@ public static function fromRouteMatch(RouteMatchInterface $route_match) {
    +    if ($scheme === FALSE) {
    +      throw new \InvalidArgumentException(String::format('The URI reference "@uri" is malformed.', ['@uri' => $relative_reference]));
    +    }
    +    if ($scheme !== NULL) {
    +      throw new \InvalidArgumentException(String::format('The URI reference "@uri" has a scheme, but only relative references are supported by Url::fromUserInput().', ['@uri' => $relative_reference]));
    +    }
    +    if ((strpos($relative_reference, '/') !== 0) && (strpos($relative_reference, '#') !== 0) && (strpos($relative_reference, '?') !== 0)) {
    +      throw new \InvalidArgumentException(String::format('The URI reference "@uri" has a non-empty but rootless path component, which is not supported by Url::fromUserInput(). The reference must begin with a "/", "?", or "#".', ['@uri' => $relative_reference]));
    +    }
    

    I also disagree with these exceptions. The messages are too technical. We already added an exception in #2423579: Url::fromUrl('user-path:/') should throw an exception when a path component without a slash is given and this is not about malformed URIs. This method should be about supporting user input.

  3. +++ b/core/lib/Drupal/Core/Form/ConfirmFormHelper.php
    @@ -34,7 +34,7 @@ public static function buildCancelLink(ConfirmFormInterface $form, Request $requ
    -      $url = Url::fromUri('user-path:/' . $options['path'], $options);
    +      $url = Url::fromUserInput('/' . $options['path'], $options);
    
    +++ b/core/modules/field_ui/src/FieldUI.php
    @@ -61,7 +61,7 @@ public static function getNextDestination(array $destinations) {
    -      $next_destination = Url::fromUri('user-path:/' . $options['path']);
    +      $next_destination = Url::fromUserInput('/' . $options['path']);
    
    +++ b/core/modules/path/src/Controller/PathController.php
    @@ -86,10 +86,10 @@ public function adminOverview(Request $request) {
    -      $row['data']['alias'] = $this->l(Unicode::truncate($data->alias, 50, FALSE, TRUE), Url::fromUri('user-path:/' . $data->source, array(
    +      $row['data']['alias'] = $this->l(Unicode::truncate($data->alias, 50, FALSE, TRUE), Url::fromUserInput('/' . $data->source, array(
    ...
    -      $row['data']['source'] = $this->l(Unicode::truncate($data->source, 50, FALSE, TRUE), Url::fromUri('user-path:/' . $data->source, array(
    +      $row['data']['source'] = $this->l(Unicode::truncate($data->source, 50, FALSE, TRUE), Url::fromUserInput('/' . $data->source, array(
    
    +++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestRedirectForm.php
    @@ -55,7 +55,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
    -        $form_state->setRedirectUrl(Url::fromUri('user-path:/' . $form_state->getValue('destination')));
    +        $form_state->setRedirectUrl(Url::fromUserInput('/' . $form_state->getValue('destination')));
    
    +++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
    @@ -1313,7 +1315,7 @@ protected function renderAsLink($alter, $text, $tokens) {
    -        $alter['url'] = CoreUrl::fromUri('user-path:/' . ltrim($path, '/'));
    +        $alter['url'] = CoreUrl::fromUserInput('/' . ltrim($path, '/'));
    
    +++ b/core/tests/Drupal/Tests/Core/UrlTest.php
    @@ -171,6 +172,101 @@ public function testFromRouteFront() {
    +      // Path without a leading slash containing a query string.
    

    Also still concerned about the leading slashes here. I think maybe we need another followup issue about destinations?

    It also concerns me that literally every use is also needing to append a leading slash. Is this method accomplishing our goal here?

xjm’s picture

Also realized that the exceptions themselves are now in several different code paths and thrown in other cases. I think this is incorrect. This method should define what's valid as user input, not what's valid as a URI. #2423579: Url::fromUrl('user-path:/') should throw an exception when a path component without a slash is given and fromUri() already take care of validating URIs.

xjm’s picture

Discussed with @effulgentsia. For #14 points 1 and 2, we agreed to rename the parameter to $user_input, to make it explicit that this is an unvalidated user string. We will remove the first two exceptions as they are redundant with the third, but include a comment explaining why it's sufficient to require the leading / # ? and append the scheme. For point 3, we will fix that in #2418219: Deprecate destination URLs that don't include the base path. @effulgentsia is also filing a related followup for the path alias storage.

xjm’s picture

Assigned: xjm » Unassigned
Status: Needs work » Needs review
StatusFileSize
new18.08 KB
new17.7 KB

Here we are; sorry for the delay. Two things are still outstanding: One, we still need the followup for path alias storage (see #16), and two, there's some docs on fromUserPathUri() that I was intending to move but now I'm not sure about. That could easily also be a followup.

Other than that, I think this is probably ready.

effulgentsia’s picture

Adding some newly filed issues to the related issues list. Will followup with a patch reroll referencing them shortly.

effulgentsia’s picture

StatusFileSize
new18.78 KB
new4.32 KB

+1 to RTBC if someone other than me approves this interdiff.

xjm’s picture

Status: Needs review » Reviewed & tested by the community

All those changes look great to me.

xjm’s picture

Issue summary: View changes

Beta eval.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 8d87f7f and pushed to 8.0.x. Thanks!

Thanks for adding the beta evaluation to the issue summary.

diff --git a/core/lib/Drupal/Core/Form/ConfirmFormHelper.php b/core/lib/Drupal/Core/Form/ConfirmFormHelper.php
index d7b557e..57a2137 100644
--- a/core/lib/Drupal/Core/Form/ConfirmFormHelper.php
+++ b/core/lib/Drupal/Core/Form/ConfirmFormHelper.php
@@ -34,7 +34,7 @@ public static function buildCancelLink(ConfirmFormInterface $form, Request $requ
     // If a destination is specified, that serves as the cancel link.
     if ($query->has('destination')) {
       $options = UrlHelper::parse($query->get('destination'));
-      // @todo Revist this in https://www.drupal.org/node/2418219.
+      // @todo Revisit this in https://www.drupal.org/node/2418219.
       $url = Url::fromUserInput('/' . $options['path'], $options);
     }
     // Check for a route-based cancel link.
diff --git a/core/modules/field_ui/src/FieldUI.php b/core/modules/field_ui/src/FieldUI.php
index 15fd97e..f15e7c7 100644
--- a/core/modules/field_ui/src/FieldUI.php
+++ b/core/modules/field_ui/src/FieldUI.php
@@ -61,7 +61,7 @@ public static function getNextDestination(array $destinations) {
         $options['query']['destinations'] = $destinations;
       }
       // Redirect to any given path within the same domain.
-      // @todo Revist this in https://www.drupal.org/node/2418219.
+      // @todo Revisit this in https://www.drupal.org/node/2418219.
       $next_destination = Url::fromUserInput('/' . $options['path']);
     }
     return $next_destination;

Fixed on commit.

  • alexpott committed 8d87f7f on 8.0.x
    Issue #2426181 by effulgentsia, mpdonadio, xjm: Add a Url::fromUserInput...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.