Problem/Motivation
I initially discovered this when my polls, embedded with entity_embed stopped working.
The reason for that was that the generated HTML tag was not replaced anymore. The reason is that & in the markup were replaced with & and then replacing those placeholders didn't work anymore because they didn't match those in #attached anymore.
See https://3v4l.org/GGWCn for a simple script to see the & => & change.
Further analysis
#2569371: Update Renderer::createPlaceholder() to not use Attribute and SafeMarkup::format() switches away from using Attribute(), which then means we no longer get the attribute-level escaping, which means we end up with arguments="arg1&arg2" instead of arguments="arg1&arg2".
This has not been a problem so far because nothing in core parses such placeholders using PHP's DOMDocument, to then serialize it again. Furthermore, this only happens when there are multiple arguments for a lazy builder. Because $arguments = UrlHelper::buildQuery($placeholder_render_array['#lazy_builder'][1]); already URL-encodes each individual parameter, which leaves only the ampersand as a problem. So other characters that should be escaped (think < and ") are not a problem.
Therefore, HEAD is currently broken right now, because placeholder markup is invalid HTML, which means DOMDocument is unable to parse it. Well, it is able, but automatically fixes it, but then #attached[placeholders] contains the placeholder without the escaped ampersand, whereas the HTML response contains it with the escaped ampersand, hence there is a mismatch, hence the placeholder is not actually replaced.
The solution is simple: ensure PlaceholderGenerator::createPlaceholder() generates valid HTML markup.
When I was trying to reproduce this problem for Berdir, I failed. Because I wanted to not set up my entire environment to reproduce this, and I tried to use a shortcut: I modified entity_embed's filter to use \Drupal\filter\FilterProcessResult::createPlaceholder(). But using that approach, I was not able to reproduce it.
That's because #2569371: Update Renderer::createPlaceholder() to not use Attribute and SafeMarkup::format() only modified PlaceholderGenerator::createPlaceholder(), and not FilterProcessResult::createPlaceholder(). Therefore, FilterProcessResult::createPlaceholder() is still using new Attribute() and therefore still has the automatic attribute escaping. If that were broken too, then FilterTestPlaceholders would have had a failure anyway.
Proposed resolution
- See #5 — Add a regression test for
PlaceholderGenerator::createPlaceholder()generating invalid HTML markup. - See #6 — Fix
PlaceholderGenerator::createPlaceholder()so it generates valid HTML markup. - See #7 — Update
FilterProcessResult::createPlaceholder()— it already has the necessary test coverage.
Remaining tasks
- Green patch.
- Review.
- Commit.
User interface changes
None.
API changes
None.
Data model changes
None.
| Comment | File | Size | Author |
|---|---|---|---|
| #10 | render_placeholder_invalid_markup-2593481-10.patch | 5.07 KB | wim leers |
Comments
Comment #2
berdirComment #3
wim leers#2569371: Update Renderer::createPlaceholder() to not use Attribute and SafeMarkup::format() switches away from using
Attribute(), which then means we no longer get the attribute-level escaping, which means we end up witharguments="arg1&arg2"instead ofarguments="arg1&arg2".This has not been a problem so far because nothing in core parses such placeholders using PHP's
DOMDocument, to then serialize it again. Furthermore, this only happens when there are multiple arguments for a lazy builder. Because$arguments = UrlHelper::buildQuery($placeholder_render_array['#lazy_builder'][1]);already URL-encodes each individual parameter, which leaves only the ampersand as a problem. So other characters that should be escaped (think<and") are not a problem.Therefore, HEAD is currently broken right now, because placeholder markup is invalid HTML, which means DOMDocument is unable to parse it. Well, it is able, but automatically fixes it, but then
#attached[placeholders]contains the placeholder without the escaped ampersand, whereas the HTML response contains it with the escaped ampersand, hence there is a mismatch, hence the placeholder is not actually replaced.The solution is simple: ensure
PlaceholderGenerator::createPlaceholder()generates valid HTML markup.When I was trying to reproduce this problem for Berdir, I failed. Because I wanted to not set up my entire environment to reproduce this, and I tried to use a shortcut: I modified
entity_embed's filter to use\Drupal\filter\FilterProcessResult::createPlaceholder(). But using that approach, I was not able to reproduce it.That's because #2569371: Update Renderer::createPlaceholder() to not use Attribute and SafeMarkup::format() only modified
PlaceholderGenerator::createPlaceholder(), and notFilterProcessResult::createPlaceholder(). Therefore,FilterProcessResult::createPlaceholder()is still usingnew Attribute()and therefore still has the automatic attribute escaping. If that were broken too, thenFilterTestPlaceholderswould have had a failure anyway.Therefore, the entire solution for this issue is:
PlaceholderGenerator::createPlaceholder()generating invalid HTML markup.PlaceholderGenerator::createPlaceholder()so it generates valid HTML markup.FilterProcessResult::createPlaceholder()— it already has the necessary test coverage.Comment #4
wim leersComment #5
wim leersHere's step one: a regression test that should fail.
Comment #6
wim leersHere's step two: the fix.
Comment #7
wim leersAnd here's step 3: updating
FilterProcessResult::createPlaceholder()to be in sync.Comment #8
wim leersComment #9
dawehnerMaybe its just me that this is super confusing, having two times the same hashing with a different algorithm, maybe we could document what is going on?
Comment #10
wim leers#9: because I'm stupid. I forgot to delete some code, lol. Good catch.
Comment #12
berdirThe tests in my install profile are working with this, we have tests here and the fix makes sense I think.
No point in waiting :)
Comment #13
dawehnerThe only thing which is a bit confusing is that this doesn't test the actual output of the code, should we check that as well? It is just checking whether the placeholder is valid, but I guess we have other places which at least explicitly check that?
Comment #14
wim leers#13:
\Drupal\Tests\Core\Render\RendererPlaceholdersTesttests it implicitly. The thing is that the exact placeholder doesn't matter and shouldn't matter; it's not an API; it's merely a placeholder that must be reliable. It's the reliability that was broken and that we fix and test here.If people feel strongly we should explicitly test the generated markup, happy to add test coverage for that.
Comment #15
dawehnerI was just wondering ...
Comment #16
effulgentsia commentedDiscussed with other committers, and agreed this is a sensible fix in RC.
Comment #17
catchCommitted/pushed to 8.0.x, thanks!
Comment #19
wim leersYay, thanks! :)