Problem/Motivation

We are getting more use cases for escaping if given string is not safe. We see code patterns like:
\Drupal\Component\Utility\PlaceholderTrait::placeholderFormat():

    foreach ($args as $key => $value) {
      switch ($key[0]) {
        case '@':
          // Escaped only.
          if (!SafeMarkup::isSafe($value)) {
            $args[$key] = Html::escape($value);
          }
          break;

        case '%':
        default:
          // Escaped and placeholder.
          if (!SafeMarkup::isSafe($value)) {
            $value = Html::escape($value);
          }
          $args[$key] = '<em class="placeholder">' . $value . '</em>';
          break;

And in views_pre_render_views_form_views_form():

  foreach ($substitutions as $placeholder => $substitution) {
    $search[] = Html::escape($placeholder);
    // Ensure that any replacements made are safe to make.
    if (!SafeMarkup::isSafe($substitution)) {
      $substitution = Html::escape($substitution);
    }
// ..

Proposed resolution

Follow up to #2569699: Remove SafeMarkup::checkPlain() for Drupal 9.0.x to comment #30 by @alexpott:

+++ b/core/includes/batch.inc
@@ -102,6 +103,14 @@ function _batch_do() {
+  if (!SafeMarkup::isSafe($message)) {
+    $message = Html::escape($message);
+  }

Is it time to add SafeMarkup::escapeIfUnsafe() it seems we have quite a few issues that need this.

Remaining tasks

  • Create method SafeMarkup:: escapeIfUnsafe()
  • Find use cases and replace them
  • Write tests
  • Review

User interface changes

None.

API changes

API addition to SafeMarkup?

Data model changes

None.

Comments

iMiksu created an issue. See original summary.

upchuk’s picture

Assigned: Unassigned » upchuk
upchuk’s picture

Assigned: upchuk » Unassigned
Status: Active » Needs review
StatusFileSize
new742 bytes

Something like this?

imiksu’s picture

Assigned: Unassigned » imiksu
Issue summary: View changes
Status: Needs review » Needs work

Yyup something like that, I'll try to put it in use in couple of places and see if any tests fails.

imiksu’s picture

Assigned: imiksu » Unassigned
Status: Needs work » Needs review
StatusFileSize
new2.29 KB
new3.02 KB

Putting to "Needs review" to review tests. Tests for this specific method are still needed. Do we need to change/update documentation somewhere?

imiksu’s picture

Issue summary: View changes
Issue tags: +Needs tests
imiksu’s picture

+++ b/core/lib/Drupal/Component/Utility/SafeMarkup.php
@@ -237,4 +237,22 @@ public static function format($string, array $args) {
+  public static function escapeIfUnsafe($string) {
+    if (!self::isSafe($string)) {
+      $string = Html::escape($string);
+    }
+
+    return $string;
+  }

Actually should we use here SafeMarkup::checkPlain() instead?

upchuk’s picture

SafeMarkup::checkPlain() will be deprecated no?

MattA’s picture

+++ b/core/lib/Drupal/Component/Utility/PlaceholderTrait.php
@@ -34,17 +34,13 @@ protected static function placeholderFormat($string, array $args, &$safe = TRUE)
+          $value = SafeMarkup::escapeIfUnsafe($value);
           $args[$key] = '<em class="placeholder">' . $value . '</em>';

+++ b/core/modules/views/views.module
@@ -666,9 +666,7 @@ function views_pre_render_views_form_views_form($element) {
+    $substitution = SafeMarkup::escapeIfUnsafe($substitution);
     $replace[] = $substitution;

There doesn't appear to be any need for these intermediate variables anymore.

imiksu’s picture

SafeMarkup::checkPlain() will be deprecated no?

That's correct, ignore my comment.

There doesn't appear to be any need for these intermediate variables anymore.

How come there's no need?

imiksu’s picture

StatusFileSize
new767 bytes
new3.77 KB
new767 bytes

Here's initial version of tests. It needs to test case where we already have safe string and it wouldn't escape it.

The last submitted patch, 11: 2573913-11-testsonly.patch, failed testing.

The last submitted patch, 11: 2573913-11-testsonly.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 11: 2573913-11.patch, failed testing.

upchuk’s picture

StatusFileSize
new847 bytes
new4 KB

Added test to cover also the safe string that needs to stay unchanged. Hope that's good :)

upchuk’s picture

Status: Needs work » Needs review
imiksu’s picture

Issue summary: View changes
upchuk’s picture

Issue tags: -Needs tests +Barcelona2015
berdir’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Component/Utility/SafeMarkup.php
    @@ -237,4 +237,22 @@ public static function format($string, array $args) {
    +   * @param string $string
    +   *   The text string to be escaped if unsafe.
    

    $string can also be a SafeString object, no? That's exactly what were are checking here. So we should document the type as string|SafeStringInterface.

  2. +++ b/core/lib/Drupal/Component/Utility/SafeMarkup.php
    @@ -237,4 +237,22 @@ public static function format($string, array $args) {
    +   * @return string
    +   *   The escaped string if it was unsafe.
    

    empty line between @param and @return (but not between multiple @param's).

    The description is also not clear what happens if the string was safe, it sounds like it doesn't return anything. Maybe just "An escaped or safe string." ?

lauriii’s picture

  1. +++ b/core/lib/Drupal/Component/Utility/SafeMarkup.php
    @@ -237,4 +237,22 @@ public static function format($string, array $args) {
    +   * @param string $string
    

    This can be also SafeString.

  2. +++ b/core/lib/Drupal/Component/Utility/SafeMarkup.php
    @@ -237,4 +237,22 @@ public static function format($string, array $args) {
    +   * @return string
    +   *   The escaped string if it was unsafe.
    

    This is not a string that its returning but instead a SafeStringInterface.

upchuk’s picture

Assigned: Unassigned » upchuk
upchuk’s picture

Assigned: upchuk » Unassigned
Status: Needs work » Needs review
StatusFileSize
new741 bytes
new4.08 KB

Here we go.

MattA’s picture

#20.2
If a SafeStringInterface is passed, then by definition it is safe and simply returned immediately. If a regular string is passed, then it is checked against the static list and either returned (if it was already marked as safe) or run through Html::escape(), which also results in a plain old string being returned.
In my opinion, I don't think the current documentation does a good job of explaining this.

Also, #9 was never addressed.

upchuk’s picture

StatusFileSize
new2.22 KB
new4.26 KB

@MattA,

Sorry, I overlooked #9. Please see the interdiff for the changes you mentioned.
As for the documentation, I think I see what you mean, I tried to adjust it.

lauriii’s picture

Status: Needs review » Closed (won't fix)

I discussed about this issue with @alexpott and @stefan.r and we agreed that we don't want to do this now. The reason why its not a good idea to add a helper for this is that in most of the cases where strings has to be manually escaped, there is better alternative than using this helper method. By adding this helper we would encourage people use this method on top of other solutions.

I also looked the use cases in core and I didn't find good use cases so far in the core where there would get much benefit out of this. I also think that this will get better by the time we get rid of SafeMarkup::isSafe().