Problem/Motivation
drupal_set_message is a little magic (because security). if the result of the t() function has a single unescaped value (e.g. !logout), then it will cause the entire message to be marked as "unsafe", which causes the message to be escaped on output. This results in images like the one below, taken after a one-time login link for the user "joe" was used while the "admin" user was logged in:

Proposed resolution
The solution to this problem is to use @login instead; this escapes the login link, which keeps the message safe. The result of this change looks like this:

Remaining tasks
As observed below, using !message with drupal_set_message will always result in double-escaping. There are not too many other places in Drupal where this pattern is used; these should all be changed to @message.
It would probably be a good idea to update the documentation of drupal_set_message to advise that this pattern should not be used.
!message might also produce double escaping when used with other methods. This should probably be a follow-on issue or issues, though.
| Comment | File | Size | Author |
|---|---|---|---|
| #10 | drupal_set_message_escaping-2425581-10.patch | 3.91 KB | greg.1.anderson |
| #10 | using_safe_data_with_drupal_set_message.png | 11.99 KB | greg.1.anderson |
| #7 | drupa_set_message_test.png | 5.26 KB | greg.1.anderson |
| #6 | drupal_set_message_escaping-2425581-6.patch | 3.25 KB | greg.1.anderson |
| #6 | broken-field-message.png | 10.28 KB | greg.1.anderson |
Comments
Comment #1
greg.1.anderson commentedHere is a patch to fix the two places where this occurs in the User module.
Comment #2
greg.1.anderson commentedComment #3
tim.plunkettI don't think this is worth an automated test, but a committer might disagree with me.
Comment #4
edutrul commented@langelhc, @AlexaBR @edutrul have done some grep into drupal core and found is fine about escaping output.
RTBC #drupalconlatino
Comment #5
greg.1.anderson commentedThanks for doing further research into this issue, @edutrul.
Instances where "!" are used when there is no html in the message are okay, because it is innocuous to check_plain() text that is already plain. The first two you quote might sometimes be an issue, though, because %label will be escaped, which will cause double-escaping due to "!message" if the contents of $values['label'] contains any content that is altered when escaped. So, these two could probably become @message.
Comment #6
greg.1.anderson commentedSo, here is a screenshot after adding
Joe's "special" field. I wasn't sure how to force the exception to be thrown, so I temporarily added a drupal_set_message() to the non-failure case just to take the screenshot.Temporary code (wrong, %label is double-escapped):
Screenshot:
I've added an updated patch to also correct this issue.
Comment #7
greg.1.anderson commentedI was thinking about this overnight, and realized that using '!message' with drupal_set_message is always a problem in Drupal 8. To demonstrate this, I ran the following test:
drupal_set_message($this->t('Test of bang substitutions: !message', array('!message' => 'This is a test')));
That produced the following output:
So, !message works the same as @message, but !message has the additional undesirable side-effect of escaping other parts of the message unrelated to the substitution that used "!". !message is therefore only usable in cases where there is no content that would be altered by output escaping. In that instance, though, @message works just as well. Therefore, the other places in Drupal 8 where this pattern occurs should be changed to use @message instead of !message, to avoid the side-effect, and reduce the chance that this pattern might spread through emulation. For example, if someone copies a druap_set_message that uses !message, and then adds an @other variable, then a double-escaping bug will be introduced. This can linger if the message comes up only rarely, or if the substituted values usually do not contain any characters that need escaping.
Comment #8
xjmComment #9
greg.1.anderson commentedComment #10
greg.1.anderson commentedOkay, I made an important realization: !message is useful and works as intended, but only if the replacement value is already marked as a safe string.
Here is a working example from Drupal 8 core, in core/modules/config/src/Form/ConfigSync.php buildForm():
Here is a screenshot of a copy of this code, executed with a hardcoded list of two items, "item 1" and "item 2":
So, the output of drupal_render() is marked as "safe", so when this safe value is used as the replacement value for !changes, above, the result of t() is also marked as safe, and no double-escaping occurs. This is different than the erroneous values above, because in the failure cases, the replacement values were simple strings that had never passed through any function that marked them safe with SafeMarkup::set(). Calling SafeMarkup::set() directly is deprecated, so the usage model for using !replacement with drupal_set_message() is to always insure that the replacement value came from drupal_render(), or some other function that will mark the result safe.
There was only one other place in Drupal core where a !replacement needed to be changed to @replacement. I have attached an updated patch that fixes it, plus #6 and #1.
Comment #11
greg.1.anderson commentedComment #12
Jeff Burnz commentedgreg.1.anderson ++, thanks for all the snooping, you saved me loads of time, cheers :)
Comment #13
subhojit777Code changes looks good and the patch fixes the problem. I agree with @tim.plunkett in #3, it is a small fix and does not needs tests. @greg.1.anderson thank you for keeping up the work in this.
Comment #14
alexpottThis issue is a normal bug fix, and doesn't include any disruptive changes, so it is allowed per https://www.drupal.org/core/beta-changes. Committed 13d4cd2 and pushed to 8.0.x. Thanks!