Problem/Motivation
check_markup() got removed in favour of SafeMarkup::checkPlain().
We kept check_plain() in Drupal 7, despite it being a one-liner for the following reasons:
1. It was backwards compatible with Drupal 6.
2. It provided a place to document exactly what sort of escaping we were doing, why, etc.
3. Should the required arguments to htmlspecialchars ever change again, or if we ever have to do additional sanitization on top of htmlspecialchars, then rather than have to update thousands of calls to it across both core and contrib, we could update the single call it it in check_plain()
#1 does not apply to Drupal 8. #2 and #3 certainly still do.
Now we're trying to minimise the safe string list by not doing unnecessary SafeMarkup::checkPlain() and (hopefully) in the end removing SafeMarkup::checkPlain().
This results in lots of patches which do:
- $link = '<a href="' . $base_url . '">' . SafeMarkup::checkPlain(\Drupal::config('system.site')->get('name')) . '</a>';
+ $link = '<a href="' . $base_url . '">' . htmlspecialchars($site_name_with_markup, ENT_QUOTES, 'UTF-8') . '</a>';
#2545972: Remove all code usages SafeMarkup::checkPlain() and rely more on Twig autoescaping already adds this as a helper. But that means that once it is in, all of those lines that were just changed to htmlspecialchars() needs to be changed again.
Opening this issue to just add it now, so that we don't need to change things twice. Also so that the patches don't hurt my eyes seeing ENT_QUOTES everywhere.
Proposed resolution
Add back the wrapper, in the form of Html::escape() which calls htmlspecialchars() to escape characters so that they are safe for HTML output.
Remaining tasks
Review and agree name.
Commit
User interface changes
None
API changes
Add new method Html::escape().
Data model changes
None
Beta phase evaluation
| Issue category | Task because we could just use htmlspecialchars() |
|---|---|
| Issue priority | Major because part of #2549943: [plan] Remove as much of the SafeMarkup class's methods as possible and necessary to complete some of the sub tasks of #2280965: [meta] Remove every SafeMarkup::set() call |
| Prioritized changes | The main goal of this issue is improving a key security API and improving performance by minimising SafeMarkup usage. |
| Disruption | Not disruptive since it is an API addition. However to get the maximum benefits contrib has to use it. |
| Comment | File | Size | Author |
|---|---|---|---|
| #70 | add_html_escape-2550945-70.patch | 950 bytes | mbovan |
| #67 | 2550945-67.patch | 14.99 KB | stefan.r |
| #67 | interdiff-62-67.txt | 607 bytes | stefan.r |
| #63 | 2550945-3.62.patch | 15 KB | alexpott |
| #63 | 62-61-interdiff.txt | 1.73 KB | alexpott |
Comments
Comment #2
catchComment #3
wim leersAnother reason to do this: the ability to invert
Html::decodeEntities()with a signature you'd expect: "encode" instead of "decode".Comment #4
alexpottLet's add tests and make it a child of the critical that definitely needs it.
Comment #5
dawehnerLooks great for me!
Comment #6
catch@xjm wasn't keen in #2545972-5: Remove all code usages SafeMarkup::checkPlain() and rely more on Twig autoescaping, so assigning.
Comment #7
alexpottOne sec - we should replace all
htmlspecialchars()in core too.Comment #8
alexpottHere we go.
Comment #9
alexpottFiled #2551107: StylePluginBase should use Html::decodeEntities() that I spotted whilst doing this patch. Kinda related.
Comment #10
catchSaw this and went hmm, there's an issue to add it everywhere at #1211866: Enable ENT_SUBSTITUTE flag in Html::escape.
Comment #12
catchAlso UTF-8 is now the default encoding since PHP 5.4, so all the places we're using it are unnecessary.
Comment #13
alexpottSo let's do #1211866: Enable ENT_SUBSTITUTE flag in Html::escape once this lands - and it seems like a really good reason to do this issue.
Comment #14
joelpittetYes please, this blocks a bunch of work we'd like to clean up in other issues.
@catch regarding #12 it doesn't look default because it's loading from ini settings, so if we exclude it and they have a different setting in their ini, it would be less predictable, no?
Reviewed the patch and DrupalCI agrees.
Comment #15
catch@joelpittet makes sense, I was looking at the PHP 5.4 change, didn't notice it had changed again in 5.6. The fact that there are three different defaults between four PHP versions is regardless a good reason to keep a wrapper though.
Comment #16
olli commentedWith this patch, we would have:
and a method that looks like the opposite:
Just to make sure, is this what we want since the opposite of htmlspecialchars() is htmlspecialchars_decode(), and the opposite of html_entity_decode() is htmlentities()?
Comment #17
alexpott#16 that is a great point - thoughts everyone?
Comment #18
wim leers#16 is excellent.
However:
So we're basically layering sanity on top of PHP's insanity, and the patch is correct AFAICT.
Comment #19
alexpottNo harm in adding some documentation.
Comment #20
alexpottSo here's why we use html_entity_decode and htmlspecialchars(). And is also another good reason to have these methods existing so all of core, contrib and custom take the same opinion.
Comment #21
alexpott@yesct spotted a grammar mistake.
Comment #22
wim leers+1, that makes a ton of sense.
Comment #24
stefan.r commentedThis looks great, +1 to using those php functions... So
Html::encodeEntities(Html::decodeEntities($text) != $textas it would still take out the unneeded HTML entity equivalents, but that's still leaving the string in a "cleaner" state.Nit: s/output/outputs, and maybe we can mention
éalong withéjust so it's totally clear what we mean?Nit: the input ($text)?
Comment #25
xjmComment #26
joelpittet@stefan.r I don't see #24.1 in #21 maybe it was fixed?
No idea on the parens around $text in the return comment, haven't run into that convention before, likely doesn't need to even have the variable and could say
'The input text with all HTML entities encoded.'RTBC++ change can be made on commit.
Comment #27
xjm@alexpott and I just discussed this at length; I don't have time to summarize the discussion unfortunately but @alexpott is going to update the patch and/or post.
Comment #28
alexpott@xjm expressed concerns that Html::encodeEntities() is not the opposite of Html::decodeEntities() and that it is possible the following code does not return
TRUE.That is highly problematic code.
Plus it becomes clear that calling the method
encodeEntities()when you read the docs in the patch:Nope it does not do this at all.
However the symmetrically of encode/decode is not the primary reason to do this patch. PHP adds features to
htmlspecialchars()that we want to use like #1211866: Enable ENT_SUBSTITUTE flag in Html::escape and having a central place makes this simple for core and possible for contrib/custom.After further discussion with @xjm and @ksenzee we decided the name should be something descriptive about what the method actually does. The new patch goes for
escapeSpecialChars(). The word escape is used because that is what is happening and specialChars because it is only special characters and to map tohtmlspecialchars()for those familiar with PHP. However given the importance of getting this name correct - maybe people have better ideas? Zend's Escaper usesescapeHtml(), the Twig filter usesescape- so perhaps we should drop theSpecialChars. This would clash withSafeMarkup::escape()(which does not always escape - magic :) ) but we have plans to remove that in #2549393: Remove SafeMarkup::escape() and rely more on Twig autoescaping.The patch also adds test coverage of the relationship between
Html::decodeEntities()andHtml::escapeSpecialChars()to prove the fact that they are not opposites.Comment #29
alexpottUpdated IS
Comment #30
alexpottComment #31
alexpottComment #32
stefan.r commented+1, this is better! I think the method name is fine (otherwise ::convertSpecialChars?)
Maybe we could clarify the list of conversions that htmlspecialchars() does in the docs, I think there's only 4 or 5.
Comment #33
alexpottre #32 I don't think we need to be that specific - what happens (very very unlikely) if htmlspecialchars() escapes a new character in PHP 7.4?
That's Zend's documentation
Here's the Twig documentation which http://twig.sensiolabs.org/doc/filters/escape.html
And for complete-ness our old check_plain() docs https://api.drupal.org/api/drupal/includes%21bootstrap.inc/function/chec...
What is common between the documentation of both Zend and Twig is that they both point out that
htmlspecialchars()is used internally.The patch attached adds this and improves the one line description to say exactly what it does.
Comment #34
catchMuch better naming, I'm going to bump this back to RTBC.
Comment #35
wim leersWhy not
Html::escape()? That'd be in line with what Twig and Zend call it. #28 indicates it is possible. Is there a good reason to not go with that much simpler name?Comment #36
alexpottMy reasoning was that we're only escaping special characters and to tie in to the PHP function name. But I agree the simplicity of Html::escape() is alluring.
Comment #37
alexpottAlso it could be argue that even if we don't manage to remove SafeMarkup::escape() the fact that that ends up calling this method means that having the same name actually makes sense.
Comment #38
joelpittetI like the simplicity of HTML::escape too and likely won't be another useful escaping. There are other tools in php for escaping everything that are used less frequently.
Comment #39
alexpottI'm not going to be able to roll the patch right now @wim or @joel do either of you wanna do the refactoring honours - there also be some docs and PHPUnit method name changes. It should be enough to do a case sensitive replace escapeSpecialChars with escape and then fix comment flow issues.
Comment #40
wim leersI'll reroll.
Comment #41
wim leersComment #42
joelpittetBack to RTBC, thanks @Wim Leers. Double checked nothing was missed in the renaming.
Comment #43
joelpittetComment #44
xjmAlright, I'm more comfortable with this with the updated name, and I can see the case for adding the method to easily allow changes like #1211866: Enable ENT_SUBSTITUTE flag in Html::escape in the future.
The documentation is a little odd and has a couple grammatical errors, so I'll propose an update.
Comment #45
xjmComment #46
alexpott#33 outlines a potential reason to not list all the characters - I think if some needs that level of detail they should be referring to the documentation that does the actual escaping. I'm not sure we should not shy away from pointing developers to real PHP documentation where appropriate and we have this covered with the
@see htmlspecialchars().Comment #47
alexpottBut if we are going to do this why not copy from php.net and do something like...
But as I pointed out in #46 we already have the
@see htmlspecialchars().Comment #48
xjmI don't understand why #33 is a reason not to list out the characters. We can update the docs if we change what the method does in the future. However, I see no reason to cloak what the method does behind a layer of mystery and force people to parse what the specific PHP.net flags do -- otherwise, what is the point of having a method in the first place if not to remove the need for the developer to make these decisions?
People can tell that it uses htmlspecialchars() internally by reading the code and they can read the docs for it then if they choose to in order to see how it works. But IMO it's much better to just explain in plain English what the method does so that developers understand what "escaping" is at all. Otherwise, it hides it behind an intimidating shroud and makes developers think they cannot understand it.
Comment #49
alexpottOne has to love php.net's
'"'and"'"... cause that is easy to read :)Comment #50
xjmRe: #9 Yes, which is why I didn't do that.
Comment #51
alexpottSo if a developer needs to know what escaping is then we need to go further and tell what the character becomes as in #47.
Comment #52
xjmI disagree, but someone else can add that if they like.
Comment #53
joelpittetThe improvements to the docs help make the intentions clear. I'd likely not put
(for example)or(e.g.)in parens as it feels to be a side note/whisper/supplementary info, but don't really think that changes the core intention behind the comment fixes, so RTBC.Comment #54
joelpittetComment #55
alexpottFine by me... I think the argument around
is inconsistent when we don't say what the characters become - but that is no reason to block commit of this further.
rtbc +a gazilllion.
Comment #56
joelpittetActually I'll see if I can fix #53 and try to incorporate a compromise on #47
Comment #57
joelpittetOk wasn't sure if I should period the end of those items or not, or to quote them or not, so I decided in both cases to not because it added more visual clutter and possible confusion as brought up in #49
Anyways, hopefully this helps?
Comment #58
kgoel commentedgoing to review this
Comment #59
kgoel commentedI think you meant whereas. Replace wherearas with whereas.
I have read the entire patch and everything else looks good.
Comment #60
kgoel commentedI am confuse. If I understand correctly escape() will change special characters to HTML entities and decodeEntities() will change HTML entities to special characters. Maybe make doc block less confusing?
Comment #61
joelpittetOk I believe the problem @kgoel was having was a bit of double negatives going on so I tried to refactor it a few times to get it into a positive form that can be slightly easier to grasp.
So as to avoid the confusion of the equivalent to
if (!!$true).Fixes #59 as well.
Comment #62
kgoel commented@joelpittet, thank you for improving the doc block. RTBC pending on test bot
Comment #63
alexpottI think we can improve the documentation by having shorter and more concise sentences.
Comment #64
kgoel commented@alexpott, I like the doc block and the difference between Html::decodeEntities() and HTML::escape() is very clear.
Comment #65
joelpittetThis looks like it covers all the things. RTBC++
Comment #66
stefan.r commentedI think this looks good now
Just
'as we don't have the ENT_HTML5 flag set, so we never translate it to'Comment #67
stefan.r commentedComment #68
catchSo I uploaded the first patch, but that was just hacked from #2545972: Remove all code usages SafeMarkup::checkPlain() and rely more on Twig autoescaping, haven't actually written anything for this issue. Since xjm and alexpott both have at this point, I think that leaves me to commit it.
I did a very basic update to https://www.drupal.org/node/2302363 to swap the reference from SafeMarkup::checkPlain() to Html::escape(), but that's going to need a lot more editing as this work continues anyway.
Committed/pushed to 8.0.x, thanks!
Comment #70
mbovan commentedThis breaks Drupal installation for me: Mac OS X, PHP 5.5.25, MySQL 5.6.24.
I think the problem is that we have
Htmlclass in the same directory asHtmlTagclass and when we try import\Drupal\Component\Utility\HtmlPHP is confused with other class.Quick fix would be to add alias for imported class or to use full namespace when needed.
Providing a patch.
Comment #71
aspilicious commentedComment #72
dawehnerI always wondered why there is not tool out there which detects those problematic lines of code ...
Comment #73
alexpott@mbovan yeah we've seen this before - it's a PHP bug. We had this in the plugin system somewhere. Yep - see #2170471: ContextAwarePluginBase compromised after commit of Core PluginBase and https://bugs.php.net/bug.php?id=55068
Comment #74
alexpottFYI: This is happening because we have
Drupal\Core\Render\Element\HtmlComment #75
alexpott@dawehner there doesn't need to be a tool - PHP was fail at compile time.
Comment #76
dawehnerAh that just fails in case both Html and HtmlTag is loaded, right?
So what about writing a test which requires all classes ;)
Comment #77
alexpott@dawehner yeah but we do that in the installer and it's not breaking... there is something environment about this.
Comment #78
alexpottI'm getting this locally - I have no idea why this is not happening on testbot - let's fix and discuss adding a failing test in a followup. Although this test is not going to catch a new render element that uses Html. php--
Comment #80
alexpottWe got a test pass on DrupalCI so I cancelled the pift test.
Comment #82
catch