Follow-up to #2506427: [meta] !placeholder causes strings to be escaped and makes the sanitization API harder to understand
Problem/Motivation
Following #2399037: String::format() marks a resulting string as safe even when passed an unsafe passthrough argument, using a !placeholder
in a formatted string essentially guarantees that the entire string--not just the placeholder--will be double-escaped when rendered.
I think this behavior is a bug. However, as @effulgentsia points out, many uses of !placeholder
are either misuses or not necessary now that @
supports checking the safe strings list.
Proposed resolution
Remove all usages of !placeholder
from SafeMarkup::format()
by means of:
- Remove
SafeMarkup::format()
from Exception messages @see https://www.drupal.org/node/608166 - Remove
SafeMarkup::format()
fromassertText()
messages as it makes them less helpful. - Replace the
!placeholder
with@placeholder
inSafeMarkup::format()
to ensure unsafe values are escaped. - Exclude changing the explicit tests for
!placeholder
inSafeMarkupTest.php
See parent issue for beta evaluation. #2506427: [meta] !placeholder causes strings to be escaped and makes the sanitization API harder to understand
Remaining tasks
Review- Commit
User interface changes
None.
API changes
None.
Comment | File | Size | Author |
---|---|---|---|
#30 | 2564353-2.30.patch | 27.17 KB | alexpott |
#30 | 20-30-interdiff.txt | 3.74 KB | alexpott |
#20 | 2564353.20.patch | 27.51 KB | alexpott |
#20 | 12-20-interdiff.txt | 1.81 KB | alexpott |
#19 | 2565241.2.patch | 2 KB | alexpott |
Comments
Comment #2
alexpottThe only change I think is a bit dodgy is core/modules/locale/locale.pages.inc since the is a bit
SafeMarkup::format('@onefish @twofish')
.This code actually fixes some bugs in the config importer that look to be tested but actually are not. :)
Comment #3
alexpottSo re
core/modules/locale/locale.pages.inc
I think we should aim to remove the entiretemplate_preprocess_locale_translation_update_info
function and put all the logic in the template but that does not need to be done as part of this issue.Comment #4
joelpittetA bunch of these are covered off in #2506445: Replace !placeholder with @placeholder in t() and format_string() for non-URLs in tests so added it as related.
This looks like a bug you mentioned, nice catch! Although does this doesn't seem to have to do with SafeMarkup::format(). If we are to resolve that here should we add tests here or split this off and add tests in a follow-up?
Nit: https://www.drupal.org/coding-standards#concat
and missing comma at end.
Regarding #3 totally agree. Made a follow-up #2565123: Move logic from SafeMarkup::format() from template_preprocess_locale_translation_update_info() to template
Comment #5
joelpittetI took out the config fix for now and fixed a couple of nits. I reviewed this with color-words and without and I think this is RTBC.
As you can see from the interdiff other than the nits and the removal of unrelated fixes this is the same patch.
I ran config test locally and they still passed so I'm pretty confident that change is not needed here and we can open up a follow-up (I will after this comment.) If you or testbot disagrees with my assessment we can NW this, but we likely need to "Needs tests" if we keep that bug fix in.
Comment #6
joelpittetAnd of course I missed a space and a . in the sentence changed. Of course.
Comment #7
joelpittetFollow-up added #2565139: Fix config importer error log.
Comment #10
alexpott@joelpittet those changes are necessary to remove all the
!placeholder
from SafeMarkup::format(). Let's also credit people from #2506445: Replace !placeholder with @placeholder in t() and format_string() for non-URLs in tests.joelpittet, Ryan Weal, nlisgo, subhojit777, justAChris
should be added.Comment #11
alexpottRe-uploading #2
Comment #12
alexpottApplying @joelpittet's nits.
Comment #13
joelpittetRealized the tests are fixed as to why the follow-up I created was failing:) #2565139: Fix config importer error log.
Thanks for re-applying the nits @alexpott
Comment #14
justAChris CreditAttribution: justAChris as a volunteer commentedCommenting per #10.
Also noting that disruption to translated strings should be minimal. I count a max of 3 instances of translated strings per language.
Comment #15
nlisgo CreditAttribution: nlisgo commentedComment per #10.
Comment #16
xjmThanks everyone! Hopefully the others from the other issue will comment here as well. Meanwhile: I reviewed all the changes in the patch and they all seem correct... that said, I also have a couple questions about the mixed approaches taken.
Ideally, we would split these patches up based on the type of the fix (e.g.: remove all translation/formatting with exception messages, remove undeeded assertion mesages, etc.), not as a hodgepodge of different bugs. The issue title says "from
SafeMarkup::format()
" so that would suggest to me that it was removing all the cases in core where it was used withformat()
directly (as opposed tot()
)... but the first hunk in the patch plus one other aret()
. Do any!placeholder
uses remain withSafeMarkup::format()
directly? Why is the first hunk included? Because they were in the configuration system, I guess? I'm not pushing back on committing them together since (as stated) all seem okay, but it does make the patch harder to review.Specific questions/remarks:
!
in the first place, so just use@
." I reviewed all of those changes and confirmed that in each case the placeholder is used simply in text (not inside an HTML tag or anything). Those are all fine.These fall under the category of "Don't sanitize exception messages because this unnecessarily couples the code and also the Error class sanitizes the output if you have error reporting to screen turned on, which you shouldn't in production anyway." Is that a correct summary? And is that documented somewhere as a best practice?
These changes are: "There's no point in sanitizing assertion messages, and the SimpleTest results page is XSS filtered at the end anyway even if you are running it in production, which you shouldn't." Right? And have we documented that best practice yet as well?
The second one here is another "Don't needlessly sanitize assertion messages." But why is it different from the first one then, which is just replacing
!
with@
?These are "Useless assertion messages for
assertText()
make debugging harder, so might as well just remove them." That's something we tell contributors all the time, though come to think of it, maybe a followup novice issue to explicitly say that on the methods wouldn't go amiss.This one is test code, so there's no need to sanitize it there either.
#markup
will successfully XSS filter it anyway, which is certainly sufficient for the test code.Comment #17
xjmOh also, I agree with #2 / #3 about the fishy (pun!) use of
SafeMarkup::format()
in locale, and also agree that it can be considered out of scope here. But also I'm not 100% clear on what is in scope. ;)Comment #18
alexpottNew patch coming.
Comment #19
alexpottIn fact in HEAD
assertIdenticalObject
is wrong because if the objects contain HTML it wouldn't be escaped on the Simpletest results page making it much harder to actually see the differences in the object and potentially breaking the results table. It'd be awesome if we could change assertion message to have no valid html and just convert the table to use#plain_text
instead of#markup
but that seems like an awful lot of work at this point.For now the patch just replaces all the
!placeholder
with@placeholder
and does so consistently.Regarding point #16.5 actually I think this is already covered by:
Comment #20
alexpottIgnore the patch in #19... wrong issue's patch.
Comment #21
joelpittetThe line from #18.2 that makes this clear (I've reformatted the text to make it a bit more clear).
https://www.drupal.org/node/608166
Thank you for the reference.
I've updated the issue summary to clarify the proposal.
Comment #22
joelpittetAfter the scope clarification I found a missing reference that wasn't resolve in this issue:
core/lib/Drupal/Core/Installer/Form/SelectLanguageForm.php:73
!
with@
.Comment #23
joelpittetThis looks really good still, I only spotted one unreplaced
!placeholder
and the only other was the test for SafeMarkup.Comment #24
subhojit777Comment as per #10
Comment #25
xjmAlso, my question about why there are
t()
included in this patch is still outstanding as far as I can tell. It does not seem to be addressed in #18.Comment #26
xjmAlso:
I tried to do that 3 years ago and it got blocked, mired, gnarled, stuck, and sidetracked. Getting in a simple novice docs patch that tells novices that omitting the optional parameter is preferred is a much easier change. We might as well tell devs in the codebase what we tell them in reviews.
No, I don't think that covers it. The followup I'm suggesting for
assertText()
etc. would explicitly tell developers "In most cases you should omit this parameter" or something. But anyway, none of that is part of the scope of this. :)Comment #27
joelpittetRe #25
Oh I didn't explicitly say why
t()
was included in the patch above:We've changed the tests as per fixing the
!old_name
and!new_type
keys inSafeMarkup::format()
. And those failed because they needed the same input from the t() they were comparing against. I only kinda mentioned that in passing on #13, sorry should have been more explicit with my note.Comment #28
joelpittetI recant my needs work for #22. @stefan_r mentioned it was part of #2506445: Replace !placeholder with @placeholder in t() and format_string() for non-URLs in tests because it's a
!url
pattern, so can be ignored for this issue.Comment #29
joelpittetCreated a follow-up for #22 as it really should be a t() anyways.
#2565981: Remove $this->t() and SafeMarkup::format() in SelectLanguageForm
Comment #30
alexpottDiscussed with @xjm, instead of removing
SafeMarkup::format()
from some assertion messages just swapped!
for@
.Comment #31
joelpittetStill RTBC from my stance, did a --color-words to review the changes were more
!
to@
and less removal of unnecessary (scope creepy)SafeMarkup::format()
sUpdated Proposal to make that more clear.
Comment #33
xjmSo I asked for elaboration on #27 in IRC. This:
array('old_type' => $old_entity_type_id, 'new_type' => $new_entity_type_id, 'old_name' => $names['old_name'], 'new_name' => $names['new_name'])
...is not a valid placeholder array. Let's zoom in again:
array('old_type'
There's no placeholder token at all. And not only that, but we were testing for there being no placeholder token, with
SafeMarkup::format()
with!placeholders
that are justplaceholders
. So obviously we can't update those tests without also updating the strings to be not broken, and it'd be silly to move that into a separate blocking issue.This was the only change in the patch that wasn't obvious to me. From the context of this preprocess, this
info
variable really could be anything, so I checked with @alexpott. This is set in /TranslationStatusForm::prepareUpdateData() to the result of TranslationStatusForm::createInfoString(), which always returns exactly somet()
. So it's already in the safe list and there is no change to it by using@
. Related followup: #2565123: Move logic from SafeMarkup::format() from template_preprocess_locale_translation_update_info() to templateCommitted and pushed to 8.0.x. Thanks! (The followup is only tangentially related at this point so wasn't worth holding the commit for.)
Comment #35
alexpottI cancelled the pifr test cause it was going to fail because the patch had been committed.