Follow-up to #2557113: Make t() return a TranslationWrapper object to remove reliance on a static, unpredictable safe list, #1825952: Turn on twig autoescape by default
Problem
SafeMarkup::format() is the last place where we still use the safe list. Currently, use of the safe list in SafeMarkup::format() leads to bugs as well as security issues (see #2549943: [plan] Remove as much of the SafeMarkup class's methods as possible).
Proposed resolution
Instead of a string, make t() return a FormattableString object implementing SafeStringInterface. This object will return the formatted string upon casting to string using the __toString() magic method.
Doing this will allow us to get rid of the safe list entirely.
Remaining tasks
Review current patch
Update change record at https://www.drupal.org/node/2564451
Commit
User interface changes
No
API changes
This will be disruptive to contrib in that, like t(), the return value of SafeMarkup::format() will need to be explicitly cast to string in strpos() and in array keys.
| Comment | File | Size | Author |
|---|---|---|---|
| #102 | 2559971-3-102.patch | 20.93 KB | alexpott |
| #102 | 100-102-interdiff.txt | 1.79 KB | alexpott |
| #100 | 2559971-3-100.patch | 21.15 KB | alexpott |
| #100 | 96-100-interdiff.txt | 15.15 KB | alexpott |
| #96 | 2559971-3-96.patch | 9.09 KB | alexpott |
Comments
Comment #2
stefan.r commentedComment #3
stefan.r commentedCombined patch with #2557113: Make t() return a TranslationWrapper object to remove reliance on a static, unpredictable safe list and #2558791: "!"-prefixed tokens should Xss::filterAdmin() but not affect safeness
Comment #5
stefan.r commentedComment #8
stefan.r commented@lauriii just a note, compared to #2557113: Make t() return a TranslationWrapper object to remove reliance on a static, unpredictable safe list this patch is very straightforward - see additions.txt in #3.
This issue depends on #2557113: Make t() return a TranslationWrapper object to remove reliance on a static, unpredictable safe list, #2560715: Prefer toString to __toString in renderVar for ToStringInterface and #2558791: "!"-prefixed tokens should Xss::filterAdmin() but not affect safeness so we may need to wait on those...
Comment #9
nlisgo commentedComment #10
stefan.r commentedComment #11
stefan.r commentedComment #12
dawehnerThe patch doesn't apply at all anymore, so better start with a minimal approach again.
Comment #15
alexpott@dawehner once
SafeMarkup::checkPlain()(or even before) we can just take over the whole SafeMarkup class :)And now SafeMarkup::format also delays escaping to the last possible moment making it compatible with the work in #2509218: Ensure that SafeString objects can be used in non-HTML contexts
Comment #16
alexpottOnce this is in and #2569699: Remove SafeMarkup::checkPlain() for Drupal 9.0.x lands we can completely remove the safe list and their associated methods - yay!
Comment #19
lauriiiWorking on the test fails
Comment #20
lauriiialexpott is working on this instead.
Comment #21
alexpottSo we cast to array in the entity query system and that is interesting for objects.
Also we need #2568977: Replace SafeMarkup::format() in the link generator - it's a bad example to everyone so the patch includes that.
Comment #22
stefan.r commentedComment #24
alexpottThe interdiff to read missed this.
Comment #25
alexpottLocale.Drupal\locale\Tests\LocalePluralFormatTest is fixed by #2570107: Make format_plural() return a PluralTranslatableString object to remove reliance on a static, unpredictable safe list
Comment #27
alexpottRolling with #2570107: Make format_plural() return a PluralTranslatableString object to remove reliance on a static, unpredictable safe list
Comment #29
alexpottGetting fixing from #2570107: Make format_plural() return a PluralTranslatableString object to remove reliance on a static, unpredictable safe list
Comment #31
lauriiiRemoving the parameter probably requires also changes for the docs also
Over 80 chars
@return missing
Over 80chars
Missing newline
Unused
Space should be before the return
Unused
Newline missing
Comment #32
nlisgo commentedRe-roll needed before addressing feedback in #31 since the following issue was fixed: #2568977: Replace SafeMarkup::format() in the link generator - it's a bad example to everyone
Comment #33
nlisgo commented#31.1 was addressed in #2568977: Replace SafeMarkup::format() in the link generator - it's a bad example to everyone
This patch addresses all other feedback in #31.
Comment #34
nlisgo commentedThe reason why the patch is much smaller in #32 than in #29 is because the issue #2568977: Replace SafeMarkup::format() in the link generator - it's a bad example to everyone implemented some of the same changes as the patch in #29.
Comment #39
lauriiis/new class instance/new PluralTranslatedString object
I don't think this is tied for string translation being a service
If we are going to commit this one, maybe it would be a good idea to point the exact issue which allows fixing this @todo
Comment #40
alexpottThis needs #2570107: Make format_plural() return a PluralTranslatableString object to remove reliance on a static, unpredictable safe list so postponing.
Comment #41
alexpottWe should make #2570037: Remove the ability to return unsafe string from Translation->translate() do @todo and re-title it do be to clean up both translation and safemarkup format
Comment #42
alexpottComment #43
lauriiiWe could add a link for the issue where this should be removed to ensure it gets done. Otherwise this is RTBC for me.
Comment #44
stefan.r commentedPatch looks great to me!
It feels like a bit of a cruel joke that SafeMarkup::format() now returns a SafeMarkup object, but why not :)
Comment #46
stefan.r commentedComment #51
stefan.r commentedToday at Drupalcon BCN there was a discussion about how there ought to be no valid use cases for extensive use of markup in either t() or SafeMarkup::format(), how only a limited set of tags ought to be used (a em/i strong sup sub dir pre code cite), and how the word "safe" could suggest something other than what we intend it to mean.
Considering t() outputs a TranslatableString object, and SafeMarkup::format() returns an untranslatable version of this, could we come up with a more appropriate name for the return object? @pwolanin mentioned
FormattedString?Comment #52
lauriiiComment #53
lauriiiCreated FormattableString class. Also probably fixed the test failure.
Comment #54
stefan.r commentedDiscussed this with @joelpittet and he was also +1 on the name change to FormattableString
Why are we removing this?
Comment #55
stefan.r commentedIf #2557113: Make t() return a TranslationWrapper object to remove reliance on a static, unpredictable safe list was critical, this one probably should be as well?
@lauriii could we also have this use ToStringTrait and such and have this return a regular string on render if we use !placeholders? Also see TranslatableString::render()
Comment #56
lauriii#54: Because this was removed2
Comment #57
alexpottThis is just wrong. We need to get #2571909: CommentForm selects using the user formatted name in.
This needs a test
There was a better fix for this earlier - we should be using SafeMarkup::setMultiple() until we remove it. Because SafeMarkup::checkPlain() is still a thing.
Comment #58
stefan.r commentedTalked to @xjm & @catch and this ought to be critical
Comment #59
dawehnerassert for string
What about using if (instance oSafeStringInterface($value)) { $value = (string) $value; }
Explain why and cast to string + explicit test coverage in
\Drupal\Tests\Core\Template\AttributeTestComment #60
larowlanOver in #2571909-32: CommentForm selects using the user formatted name when
hook_user_format_name_alter()returns aSafeStringInterface,Drupal\user\Tests\Views\HandlerFilterUserNameTestends up failing so I expectDrupal\user\Plugin\views\filter\Namewill also need updating here once the @todo inuser_hooks_test_user_format_name_alter()(which points here) is resolved.Comment #61
stefan.r commentedThis will fail
Comment #62
stefan.r commenteddiscussed with @larowlan and #60 should actually be fine after all
Comment #63
dawehnerWon't that cause issues with objects again?
Comment #68
stefan.r commented#63 yes but alex says it's wrong (#57.1) - we need to get #2571909: CommentForm selects using the user formatted name in
Comment #72
stefan.r commentedComment #75
stefan.r commentedComment #80
stefan.r commentedComment #83
plachWhat about just
DynamicString?Comment #84
plachLet me try to fix that failure...
Comment #85
plachI didn't pay attention to the clock, sorry
Comment #86
stefan.r commentedComment #87
stefan.r commentedwith placeholders.. and only safe inside HTML fragments
to be formatted
Comment #90
jhedstromTaking a look at the failures.
Comment #91
jhedstromThe 2 fails here are related to this change:
since
$author_idis always null unless the user has theadminister commentspermission.I think #2571909: CommentForm selects using the user formatted name is attempting to address this same area of code though, so perhaps this should be postponed?
Comment #92
almaudoh commentedThe verbose output from the test displays the uid and not the username on the comment form, so I'm wondering why this change was made.
Comment #93
stefan.r commentedThese bits are unrelated to this current patch - i had merged in the patch from #2571909: CommentForm selects using the user formatted name so please copy any feedback to that issue.
Comment #94
almaudoh commentedOk, thanks for the clarification @stefan.r. Then we have to wait for that issue to get fixed.
Comment #95
lauriiiPostponing this on #2571909: CommentForm selects using the user formatted name. Applied also the patch that we can keep working on after this has been unblocked.
Comment #96
alexpottRerolled using #95.
Comment #99
dawehnerThat comment doens't explain anything
Comment #100
alexpottI think we should add any new assertions in followups. Also if we use SafeStringTrait we get a ::create() method we don't want so reverting that change.
I've moved all the docs so the placeholdering docs are on the PlaceholderTrait and all the standard places point to it.
Comment #101
pwolanin commentedSome minor problems with the code docs:
"if necessary" seems wrong here.
This comment doesn't make sense here
This doesn't seem right - we get back an instance of this class
unexpected or unsafe (or both)?
should we add a note now that this is about to be removed?
Comment #102
alexpottI had a change of heart about FormattableString::create() whilst rolling the patch - oops - thanks for the review @pwolanin.
Comment #103
stefan.r commentedWe could now deprecate SafeMarkup::format() in favor of FormattableString::create()?
Could we just keep the assertions instead or changing behavior from TranslatableString by casting input to string? They were green, and I'm happy work on this and to copy-paste tests from TranslatableString.
Comment #104
dawehnerI'm not 100% convinved that the FormattedString is the best place for this, but well, the FormattedString will appear in many places.
but at least for have the @see here
Comment #105
pwolanin commentedThanks for the doc fixes.
@stefan.r - it doesn't have a create() method, I think alexpott was just commenting as to why the docs seemed mismatched.
A create() method wouldn't be useful anyhow since you can't give it args according to that interface?
Comment #106
alexpott@stefan.r yep hence the deprecate here... :)
I decided against the ::create() method to keep this aligned with TranslatableString...
Comment #107
stefan.r commentedAh so "new FormattableString('string', [args])"
If we really wanted a ::create() method we could have one on TranslatableString as well but just using the constructor is fine as well.
This bit is different to TranslatableString (which has an assert for strings). Accepting objects might be a nice feature at some point but shouldn't we stick to strings only for now and fail when input is not a string?
Comment #108
stefan.r commentedSo we could either put further documentation in the existing t() change record at https://www.drupal.org/node/2564451 or create a new one?
Comment #109
alexpott@stefan.r so the strtr in HEAD does the cast to string... so SafeMarkup::format() supports objects. The reason to cast to string when creating the Formattable string is so that if there is any execptions thrown then it works as expected rather then throwing the exception in FormattableString::__toString() - which would just break.
Comment #110
stefan.r commentedwait, we removed the ToStringTrait? why not use that here?
It's a code smell if we're passing in objects IMO. I don't think we do it anywhere in core anyway?
@dawehner wanted a check in TranslatableStirng as well - maybe check with him. think it ought to be the same for FormattableString (even if SafeMarkup::format() used to accept objects)
Comment #111
dawehnerI'm fine with dropping any kind of asserts, give the problems we have with asserts in the first place, sadly.
Comment #112
stefan.r commentedUntil now "let's add any assertions in followups" has meant they don't materialize... that's a problem as well.
I think they're great and that the quote problem is solvable by forcing single quotes and a comment above asserts until we require PHP7... this is solvable in core and contrib in the testbot and in the release publishing process - that would kill the "people might copypaste into custom modules" argument.
but even an exception would be better than nothing here - it's not like an is_string is expensive.
Comment #113
plachs/it/they
Wouldn't
::length()be more appropriate?I think we are supporting @ and % into attributes with the changes introduced in #2571673: Convert Views t() usage where it is used as an attribute value, aren't we? The result will be converted to plain text and then escaped. We will need to update these docs over there if this lands before that one, as it appears likely.
Comment #114
stefan.r commented@plach then what about #2568977: Replace SafeMarkup::format() in the link generator - it's a bad example to everyone, have those concerns been addressed now?
Comment #115
alexpott@plach the count behaviour is a Twig hack
Comment #116
plach@stefan.r:
URLs are still a special beast, but with the changes over there we should have built-in support for the normal attribute values, I think
Comment #117
xjmNote that this will conflict with #2570431: Document that certain (non-"href") attribute values in t() and SafeMarkup::format() are not supported and may be insecure because it moves the docs that is updating.
Comment #118
xjmAlso I don't see the change record linked in the sidebar?
Comment #119
dawehnerWorking on the CR
Comment #120
dawehnerThere it is, also adapted https://www.drupal.org/node/2296163
Comment #121
catchFixed #113.1 locally and committed/pushed to 8.0.x, thanks!