Problem/Motivation
SafeStringInterface is badly named for two reasons:
- The output is not necessarily safe
- The output is more than just a string - it is a string formatted for output in the HTML context
Proposed resolution
Rename SafeStringInterface to MarkupInterface and move it to Drupal\Component\Render and do quite a few other changes to make this consistent:
Drupal\Component\Utility\FormattableStringtoDrupal\Component\Render\FormattableMarkupDrupal\Core\StringTranslation\TranslatableStringtoDrupal\Core\StringTranslation\TranslatableMarkupDrupal\Component\Utility\PlainTextOutputtoDrupal\Component\Render\PlainTextOutputDrupal\Component\Utility\SafeStringTraittoDrupal\Component\Render\MarkupTraitDrupal\Core\Render\SafeStringtoDrupal\Core\Render\MarkupDrupal\Core\Field\FieldFilteredStringtoDrupal\Core\Field\FieldFilteredMarkupDrupal\filter\Render\FilteredStringtoDrupal\filter\Render\FilteredMarkupDrupal\views\Render\ViewsRenderPipelineSafeStringtoDrupal\views\Render\ViewsRenderPipelineMarkup
The basic reason why this change makes sense is that it clarifies that the purpose of the entire render system is to produce markup. Another reason is that aligns the names with Twig_Markup a class that Twig provides something similar.
Is the disruption worth it? @alexpott thinks so - the more clarity this system has the better.
Remaining tasks
Commit
User interface changes
No
API changes
Yes, some recently introduced classes will be renamed, as well as some classes that had existed for longer but are confusingly/harmfully named.
MarkupInterface is not @internal anymore
| Comment | File | Size | Author |
|---|---|---|---|
| #78 | 65-76-interdiff.txt | 6.46 KB | alexpott |
| #78 | 2576533-2-76.patch | 236.62 KB | alexpott |
| #65 | 2576533-65.patch | 233.64 KB | stefan.r |
| #65 | interdiff-60-65.txt | 2.47 KB | stefan.r |
| #60 | 2576533-57.patch | 230.93 KB | stefan.r |
Issue fork drupal-2576533
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
- 2576533-rename-safestringinterface-to
compare
Comments
Comment #2
alexpottComment #3
alexpottComment #4
dawehnerSafeStringInterface is certainly now a bad name, given how it evolved over time. We need to make sure that MarkupInterface also matches the idea of EscapedString.
So do we consider the EscapedString the final markup representation of a string?
We can easily provide a BC layer here, so if needed, there is no disruption. On the other hand, quite some of those classes have been added since the last beta.
Comment #5
alexpott@dawehner also I think if we leave a BC layer then we've got the problem of confusion.
Comment #6
alexpottThinking a bit more - the BC layer is the SafeMarkup::format() / SafeMarkup::checkPlain() / TranslationManager::translate() / TranslationManager::formatPlural() / t() / format_plural() / TranslationWrapper.
Comment #7
stefan.r commented+1 to renaming this, SafeString is not a very descriptive name. Could we deprecate + trigger_error() rather than remove completely?
Given the input of t()/TranslatableString is markup as well, I don't see what's tricky about TranslatableString - it's exactly the same as FormattableString except it's translatable so its name should stay consistent. So probably either TranslatableString/FormattableString or TranslatableMarkup/FormattableMarkup? The only worry with the latter is that the docs specifically state to use no (or minimal) markup in the input string.
Can we prohibit access to this more strongly than just marking @internal? Ie fail if called from outside of core?
Comment #8
alexpottWorking on this.
Comment #9
alexpottRenames and moves all-teh-things. Including TranslatableString.
@stefan.r this patch contains 0 logic changes - doing something more to prevent people using
SafeString/Markupwould be a logic change and is therefore out-of-scope. Also I feel we are moving on to very dangerous territory if we are always trying to implement things that stop developers from doing things - it is feel to document that it should not be used and each module should create its own object if it needs to do this sort of thing but have a runtime check about were code is used feels very heavy handed.Comment #12
alexpottI think if we do this (and I think we should) we should do it before #2575615: Introduce HtmlEscapedText and remove SafeMarkup::setMultiple() and SafeMarkup::getAll() and remove the static safeStrings list and #2570431: Document that certain (non-"href") attribute values in t() and SafeMarkup::format() are not supported and may be insecure because this patch changes slightly the direction that those patches will take.
For #2575615: Introduce HtmlEscapedText and remove SafeMarkup::setMultiple() and SafeMarkup::getAll() and remove the static safeStrings list,
EscapedStringInterface/EscapedStringbecomesEscapedMarkupInterface/EscapedMarkup.For #2570431: Document that certain (non-"href") attribute values in t() and SafeMarkup::format() are not supported and may be insecure, I think the documentation is made way simpler by the new class names making it more obvious what we consider to be markup and what is not.
Comment #13
alexpottBinary files suck.
Comment #14
alexpott<3
Comment #15
dawehnerTranslatableMarkup feels weird
At some point we could even have XssFilteredMarkup
Comment #16
wim leers+1000 for the concept. This HUGELY improves understandability, and thus DX.
Comment #19
stefan.r commentedI agree it's a slippery slope, but what's also dangerous is contrib doing Markup::create() on unsafe markup - so it may be worth checking that internal actually means internal here as I feel this needs stronger dissuasion than just an @internal annotation and there's no such thing as a @dangerous annotation. If not a runtime check (and knowing it's out of scope here), I'd also be happy with an assert() or that codesniffer check on contrib releases we discussed, with a warning on unsafe projects.
I thought the same thing but then embraced the weirdness... it actually clarifies that the input and output are markup - it's markup that can be translated to other languages.
Comment #20
alexpottFixing test fails.
@stefan.r I still think that trying to lockdown Drupal\Core\Render\Markup is out-of-scope for this issue - if you want to try you can always open a new issue - that said I don't see a performant and good way to do this and also it just goes against the grain of open source. It is enough that it is marked final.
Comment #21
catchPostponed #2569485: Add AttributeSafeStringInterface and UriAttributeSafeStringInterface, that should end up duplicate if this happens. fwiw I like this issue very much, the ambiguity of 'SafeString' is partly why the full scope of our problems wasn't realised until quite late, and this makes its purpose much more concrete.
Comment #22
wim leersThis is confusing WRT
\Drupal\Core\Render\PlaceholderGeneratorInterface+\Drupal\Core\Render\PlaceholderGenerator.They are vastly different kinds of placeholders, yet are in the same namespace, which suggests they're kinda the same thing.
… except they're not in the same namespace: these live in
Component, the ones I mentioned live inCore. Still, this is the most concerning part about this patch.This makes so much sense that I basically don't have words for it. Except that it hurts. It hurts to see the old version and how painfully much better this is.
Comment #23
dawehnerWhat about use MarkupPlaceholder and RenderPlaceholder?
Comment #24
stefan.r commentedMaybe this can just be documented in the PHPdocs, these "placeholders" are so vastly different that I think it's fine.
Otherwise PlaceholderTrait could also just be renamed or moved to Component/Utility...
Comment #25
stefan.r commentedThat could also work
Comment #26
alexpottI think the PlaceholderTrait needs to be alongside MarkupInterface because it is concerned with how to do replacements in markup with an eye on safeness and the context of where the placeholders are used.
Comment #27
alexpottWe could also just punt on the PlaceholderTrait name - since it is highly unlikely to have usages outside of FormattableMarkup, TranslatableMarkup and PluralTranslatableMarkup.
Comment #28
pwolanin commentedOverall direction here looks reasonable and I think is important to clarify what's happening.
Changing PlaceholderTrait seems much less important than the others, so I'd just leave it.
Comment #29
stefan.r commentedAgreed, just documenting they're different things should be fine
Comment #30
wim leersIsn't
PlaceholderTraitactuallyTranslationPlaceholderTrait? It's specifically for@something,:somethingand%something. All of which are translation syntax thingies. It just happens to be that translations can also contain markup. But first and foremost it seems to be about translations.Comment #31
stefan.r commentedHmm that's correct but it may add to the confusion as these placeholders are used in both TranslatableString (t()) and FormattableString (SafeMarkup::format()) - the first is for translations, the second isn't but otherwise acts the same.
Comment #32
yesct commentedComment #33
webchickIf we're going to do this, it needs to be done in the next ~5 days. Probably ideal to get it in before beta16 (tomorrow) if possible.
Comment #34
dawehnerSeems to be a good solution for now. SafeStringInterface will be used that often that we better figure out a good name for that. The placeholder issue is comparable small against that.
Comment #35
alexpottThinking about PlaceholderTrait and it's documentation - how about we just open a followup to do improvement this wrt to its differences to render placeholder generation?
Comment #36
alexpottOpened #2577785: Remove PlaceholderTrait to fix PlaceholderTrait documentation.
Comment #37
alexpottI'll review and update all the change records if this issue lands.
Comment #38
xjmI realize this one isn't part of this issue, but "escaped markup interface" seems oxymoronical to me. If it's escaped, it's no longer markup. Asking here since it seems related to the reasoning about naming for this patch (all of which looks fine to me FWIW).
I've thought about it a bit and I agree with not retaining two of everything for 8.0.0 -- the goal is to make the API less confusing, but leaving in all those deprecated wrappers would be more confusing.
I was at first concerned about dropping all these sudden changes on everyone a week before RC with no warning whatsoever. However, actually, most of the things listed here have just been added in the past two weeks I think, aside from SafeStringInterface and its implementations, which were always marked internal anyway. Is that a correct assessment? Did I miss anything?
Comment #39
xjmAlso, answers to #38 would be good beta eval material for the IS. :)
Comment #40
effulgentsia commentedI haven't read the full patch or the issue comments yet, so this is just a drive-by question. Apologies if it was already discussed and answered:
In the issue summary:
In the patch:
I'm not quite clear how to interpret this. Is MarkupInterface a better name partially because it doesn't claim to be "safe"? But then why is isSafe() only checking MarkupInterface and not something more strictly named (e.g., a SafeMarkupInterface that extends MarkupInterface)?
Comment #41
xjmIt's not clear from the summary whether PlaceholderTrait is or isn't part of this patch. It looks like it is in the current patch.
That name seems
less unambiguousnot as obviously a good choice for me. I'd suggest something FormattableStringPlaceholderTrait... since all translatable strings are also formatted strings? But I'd suggest spinning it off into its own issue to get the other ones in maybe. #2577785: Remove PlaceholderTrait seems to only be about docs and I don't think documentation solves the confusion (which is what caused us all these SafeFoo problems in the first place, of course).Comment #42
xjmisSafe()I think we are only retaining for BC reasons, thence the inconsistency in naming. But I do see how there could be confusion between the differing intents of AnyMarkupYouLikeInterface and SufficientlySanitizedMarkupInterface. MarkupThatYourSiteWillPrintJustLikeThisInterface. MarkupThatMustNotContainOtherwiseUnsanitizedUserInputInterface. Or in other words, I do see/share the concern about the name MarkupInterface not warning people away or clarifying what the implications of implementing the interface are, whereas with SafeStringInterface, it was less clear for consumers, but more clear for people actually constructing one, that there was some expectation about what should go in there in the first place.I guess this partly depends if we are keeping the @internal on the MarkupInterface. @alexpott said something about needing to remove it (the patch does not, which is good as that would be scope creep). But to me that increases the risk as well.
Comment #43
xjmAnother concern, I might expect MarkupInterface to be the object equivalent of #markup in render arrays. But it's not, at all.
Comment #45
stefan.r commentedI think the current patch is mostly fine as is. The concerns can mostly be documented (and MarkupInterface is still @internal) so if we mention clearly in the code that the placeholders in PlaceholderTrait and PlaceholderGenerator are different things, we only have a problem if people use PlaceholderTrait without reading the docs.
But realistically it will only be used in FormattableString and TranslatableString anyway, which brings me to...
Technically not yet... But if we codify that, that may work? Could we maybe get rid of PlaceholderTrait, move its code into FormattableString, and make TranslatableString extend FormattableString?
Oh and despite how silly it sounds I actually quite like
SufficientlySanitizedMarkupInterface...Comment #47
effulgentsia commentedTo elaborate on #40, my first thought when seeing
MarkupInterfacewas yay, in a follow-up we'll be able to makeVocabulary::getDescription()return an object that implements that as a way of informing callers that what's returned is HTML and not plain-text. But, with the current patch treating MarkupInterface as a signal that the markup is also safe, we couldn't do that.Additionally, as #38 points out,
EscapedMarkupwould not be accurate, since the result is not markup, it's only HTML-encoded text.Therefore, here's some possible suggestions to think about, all open to modifying or shooting down:
HtmlStringInterface.SafeHtmlStringInterfaceor addisSafe()to HtmlStringInterface.HtmlEscapedText.RenderedMarkuporRenderedHtmlclass for the Renderer to use for its output.Comment #48
stefan.r commentedRe #47 - I agree that having a separate object signaling that something is an HTML fragment could be nice, but not sure about some of the other remarks:
Vocabulary::getDescriptionwe could still make it an object with another interface to signal it contains HTML markup that doesn't implementMarkupInterface(or whatever we call the interface that signals safeness)Markupis fine and we can document where we're not 100% correct. It might mean there's a bit of a learning curve but is that really such a problem?EscapedMarkupmay technically not be "markup", but it is not plain-text either. In isolation, its contents are technically not "markup", but they will almost always be contained within markup - and again these are all things that can be documented.So I think
EscapedMarkupis fine, as the result is escaped HTML markup, i.e. HTML markup where the '"&<> are turned into HTML entities. Technically the result would be "HTML text containing HTML-escaped HTML markup".HtmlEscapedText, it still implies plain-text, which is a confusion from D7 we want to avoid, so maybeHtmlEscapedHtmlText?SanitizedAnything/SafeAnythingand::isSafe()as it's not necessarily safe (or fully sanitized), and here misnaming things actually poses a security risk - when rendered to string a SafeString object can still contain unsanitized HTML and it's only "safe" for use within HTML fragments and not within other contexts such as JSON, HTML attributes or CSS/Javascript embedded into HTML.HTMLStringInterfaceis not really more correct thanMarkupInterfaceeither, as it only refers to HTML text or a series of HTML nodes, and excludes other contexts within an HTML document (such as anything between the "<" and ">" of an HTML tag)Comment #49
dawehnerThis is maybe a different angle from what has been discussed so far.
For me HTML, given its name, consists of markup. Whether its actual tags or whether escaped strings, its all markup. So what represents a MarkupInterface for me is an object which will be printed to the resulting HTML just as it is, without any additional massaging on the outside (internal details don't matter). An EscapedMarkup will be a string which will be printed in an escaped way to the final HTML markup, a Markup object will just print out its value to the final HTML.
Sometimes we just assume that people are stupid and by that cause so much confusion that at the end there is more harm caused.
Comment #50
alexpottRe
EscapedMarkupI think we're missing the point that the markup representation of something likeI like the <br> tagisI like the <br> tag. I think thinkEscapedHTMLandEscapedHTMLInterfaceare okay too but then we're going to need to think about how we tell Twig not to escape things. This patch changesSafeStringInterfaceto beMarkupInterfacebut given then push back onEscapedMarkupI doubt that people will be happy ifEscapedHTMLInterfaceextendsMarkupInterface- although for me this makes since the__toString()does return the representation of the string necessary to display it in markup. All of this is only relevant for #2575615: Introduce HtmlEscapedText and remove SafeMarkup::setMultiple() and SafeMarkup::getAll() and remove the static safeStrings listRe the safeness of
MarkupInterface- the point is that this interface tells Twig not to auto-escape because it contains markup - the safeness or not of doing this is up to the implementation. This is one of the long running problems with using the words safe...SafeMarkup::isSafe()is alos badly named because what it really means is that this is markup and don't escape it.Wrt. to
PlaceholderTraitthere will not be any usages in contrib or custom yet so I think it is okay to rename - so #2577785: Remove PlaceholderTrait has been repurposed to discuss and fix this.Re the suggestions in #47 - I'm not opposed to changing Markup to HTML - but afaik HTML is just a specific case of markup. One thing this is nice about
HTMLis how it ties together withDrupal\Component\Utility\HTML. If we're all happy to go withHtmlInterfaceoverMarkupInterface, then I'm happy to make that change... which would look like this...SafeStringInterface=>HtmlInterfaceFormattableString=>FormattableHtmlSafeStringTrait=>HtmlTraitSafeString=>HtmlFieldFilteredString=>FieldFilteredHtmlFilteredString=>FilteredHtmlViewsRenderPipelineSafeString=>ViewsRenderPipelineHtmlTranslatableString=>TranslatableHtmlPluralTranslatableString=>PluralTranslatableHtmlHowever there are downsides - we lose the connection with
Twig_Markupand we lose the connection with#markup- some see this connection as confusing but I don't think it is. In the current patch theRendererwhole point is to complete the#markup. Once you call render on an array when it is done the#markupelement will contain aMarkupobject.Overall I strongly favour going with
MarkupInterfaceand then discussing the correct name for the escaped thing in #2575615: Introduce HtmlEscapedText and remove SafeMarkup::setMultiple() and SafeMarkup::getAll() and remove the static safeStrings listComment #51
catch#50 has good reasons to stick to Markup.
I'm not sure the name is perfect, but it's 1000 times better than SafeString.
Comment #52
stefan.r commented+1 for Markup
Comment #53
wim leersGiven the very convincing explanation in #50 and the fact that my sole other concern (
PlaceholderTraitbeing confusing, see #22.1) is being addressed in #257775 and will remove that trait, I think this is actually ready now :)We've had a good, valuable, and IMHO important discussion here, and we are concluding that what the patch already does, is the sensible thing to do.
Hence: RTBC.
Comment #54
alexpottObviously I agree entirely with #53 I think we should get #2577785: Remove PlaceholderTrait in first as it conflicts with this and makes the discussions simpler. Plus this issue needs a good issue summary update.
Comment #55
xjmWhee!
Comment #56
stefan.r commentedI can give the reroll a try in ~15min... unless @alexpott was already working on this?
Comment #57
alexpottHere's a reroll on top of all the recent changes. I've also removed the @internal from MarkupInterface because it just is not true. I've attempted to explain how it used in core and how it does not guarentee safe-ness but certain implementations can be used safely.
Comment #59
stefan.r commentedReviewed the interdiff with patch I had in my own testing issue and didn't see anything odd - just replacements about recent changes and more SafeString -> Markup conversions. SafeStringNormalizer hasn't been renamed here but that may be followup material.
The interdiff is not really more informative than the patch itself so let's just try to get this green for now...
The SafeStringInterface -> MarkupInterface.php conversion was not done as git mv though, it would be nice if we didn't lose the version info for git blame and for reviewability
Comment #60
stefan.r commentedReuploading #57 without the bit about the drupal-8.language-enabled.php binary file just to see what the testbot thinks
As to the git mv, that didn't work for me either, I guess git decides it is a new file when it's too different despite logging a rename?
Comment #62
catchMaybe -rename-threshold would help?
Comment #63
wim leersTry
git diff -M10%.Comment #64
wim leersI found only a single nitpick. Once this is green, this is ready IMHO.
Super clear! :)
"to fast render fields" sounds wrong.
<3
<3
<3
<3
<3
Comment #65
stefan.r commentedAgree about #64.2 but that was also already on the previous docs (which is hard to see precisely because git doesn't consider it a rename :), so maybe let's not fix that here
Comment #66
catchWould be even better if this said 'The formatted text'.
Comment #67
xjmNote: @alexpott didn't mention in #57 but I helped write some of those docs. :P
Carry on. :)
Comment #68
wim leers#66: Let's rename
filter.moduletotext_format.modulein D9? :PComment #69
effulgentsia commentedChecking the credit box for @xjm per #67. Possibly other participants on this issue need to get that box checked too, but I'll leave that for whoever commits this.
Comment #70
alexpottTicking some more credit boxes. Sorry @xjm - my bad. I've discussed this with @effulgentsia and @catch in IRC and the whole idea came from swimming with @dawehner :)
Comment #71
alexpottSo
drupal-8.language-enabled.phpwas changed originally by this patch because it contains references to theTranslatableStringclass - however as this is part of an update path test based on the beta indrupal-8.bare.standard.php.gzit should be aligned with beta 12. So actually we should not have changed it fromTranslationWrappertoTranslatableStringso leaving it out of this patch is fine.Comment #72
dawehnerWhy does that not fail the component test?
I'd strongly recommend that the component test removes its strictness for comments, but people didn't liked that when I tried to change that
Comment #73
alexpott@dawehner because we decided to exclude @see :)
Comment #74
effulgentsia commented@dawehner: I think the test already excludes "@see" lines.
Comment #75
dawehnerAlright, I think we will the change record updates later.
Comment #76
stefan.r commentedGave this another look and very happy with how this looks, much clearer.
As to the change record updates, @alexpott said in #37 he'd do those once this issue was in.
Comment #77
wim leersRTBC++
Comment #78
alexpott@effulgentsia noticed we'd missing
FilteredStringplus PHPStorm was still markingMarkupInterfaceas internal so fixed that.Furthermore I fixed some references to
FieldFilteredStringComment #79
effulgentsia commented#78 looks great to me. I'll commit it once DrupalCI says it passes tests. Unless someone knocks it back before then.
Comment #82
effulgentsia commentedPushed to 8.0.x! "Needs work" for the CR updates.
I used
git apply --indexof the #78 patch in an attempt to preserve history. When I do agit log --follow, I see the retained history for most of the files, but not forMarkupInterfaceandFilteredMarkup, despite the #78 patch showing those as renames. I'm not clear on whether that history is lost forever, or still available with some magic option passed togit log. http://stackoverflow.com/questions/14832963/how-can-i-control-the-rename... claims the latter, but I don't know if that's accurate.Comment #83
effulgentsia commented@alexpott informed me that
git log -M10% --follow core/lib/Drupal/Component/Render/MarkupInterface.phpshows history. Yay!Comment #85
alexpottI've updated all of the mentions of the classes renamed in this patch in out change records. I've related this issue to the most pertinent.
Comment #88
alexpott