Discovered while working on #3216015: Generate CKEditor 5 configuration based on pre-existing text format configuration for CKEditor 5.
Problem/Motivation
\Drupal\filter\Plugin\Filter\FilterHtml::getHTMLRestrictions
Parses a string of html tags into an array that defines what tags/attributes are allowed by the filter. It allows for a wildcard UPDATE per #6: While <*> tag that represents all tags, and makes it possible to allow/disallow specific attributes for all tags.\Drupal\filter\Plugin\Filter\FilterHtml::settingsForm() does not check for this (there is zero validation for the allowed_html input 😱), which is why one can be reasonably led to believe that <*> is allowed…
That finding makes all of this wrong/irrelevant:
However, the returned array does not properly represent the config for "star" tags.
Before any parsing of the "allowed html" string occurs all
*instaces are replaced.$star_protector = '__zqh6vxfbk3cg__'; $html = str_replace('*', $star_protector, $html);Later in the code, any
attributesusing*have the star returnedforeach ($node->attributes as $name => $attribute) { // Put back any trailing * on wildcard attribute name. $name = str_replace($star_protector, '*', $name);But stars representing a tag never get un-starred, and the returned array will include the "tag" for the
$star_protectorstring. For example, when it parses a string with the "tag"<* data-donk>
There is configuration for a__zqh6vxfbk3cg__tag alongside the config for the*tag returned by default
Steps to reproduce
See above.
Proposed resolution
Drop— done in https://git.drupalcode.org/project/drupal/-/merge_requests/998/diffs?com...<*>while parsing::getHtmlRestrictions()Add test coverage proving that— done in https://git.drupalcode.org/project/drupal/-/merge_requests/998/diffs?com...<*>inallowed_htmlhas no effectAdd form-level validation.— done in https://git.drupalcode.org/project/drupal/-/merge_requests/998/diffs?com...
Remaining tasks
None.
User interface changes

API changes
None.
Data model changes
None.
Release notes snippet
None.
| Comment | File | Size | Author |
|---|---|---|---|
| #43 | 3226368-43.patch | 22.87 KB | andriy khomych |
| #40 | 3226368-40.patch | 7.19 KB | lawxen |
| #33 | interdiff_30-33.txt | 1.7 KB | ranjith_kumar_k_u |
| #33 | 3226368-33.patch | 7.08 KB | ranjith_kumar_k_u |
| #30 | 3226368-30.patch | 7.1 KB | ranjith_kumar_k_u |
Issue fork drupal-3226368
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:
- 3226368-filter-html-asterisk
changes, plain diff MR !998

Comments
Comment #3
wim leersComment #4
wim leersTo get us started, creating test coverage that exercises all different aspects. See 431262b1d5706eb1bbddbabf7c20ae3408d34b0e.
Comment #6
wim leersFix is easy, but it's not actually a bug
Thanks to that first commit, adding explicit test coverage for the reported bug is very simple. And the fix is tiny too. See for yourself:
⚠️ However … that made me realize that
FilterHtmlwas simply never designed to accept and process/enforce<*>as an allowed tag! 😬Understanding
FilterHtmlFilterHtmlonly returns<*>in its restrictions because it's the only way it can communicate that additional restrictions apply/are enforced that cannot be configured. The stripping ofstyleandon*attributes is a very deeply rooted mechanism that cannot be disabled or overridden in Drupal for security reasons. Analogously yet differently,langanddirare allowed on every tag by the HTML filter (again with no configuration possible, hence "analogously") to allow proper rich multilingual support throughout user-entered content without the need for perfectly accurate configuration (which would otherwise allow multilingual Drupal sites to easily be broken).Those facts combined fully explain this piece of code in
FilterHtml::getHTMLRestrictions():Finally, you can also see in
\Drupal\filter\Plugin\Filter\FilterHtml::process()that<*>is explicitly not allowed/is ignored — again confirming that this is solely a way to convey hardcoded behaviors.But then why even have this "star protector" parsing mechanism?
Well, as the code indicates: for allowing trailing-wildcard-attribute names! Specifically:
on*(for disallowingonClicket cetera) anddata-*(for allowing arbitrarydata-attributes).In the "restrictions interpreting" code:
and in the "restrictions applying" code: see
\Drupal\filter\Plugin\Filter\FilterHtml::filterAttributes().IOW: the "star protector" stuff in
::getHTMLRestrictions()was only ever intended for thedata-*use case (attributes!), it just happens to accidentally also partially work for the<*>use case (tags!).The solution is therefore simple: tighten the parsing logic to ignore
<*>. It also means the bug originally reported is not the actual bug…Comment #7
longwaveNot sure this is a bug report, as I don't see anywhere in FilterHtml that says
*is allowed for tags - whilegetHTMLRestrictions()allows*tags, the FilterHtml docs only mention attributes.edit: crosspost with above, which explains it in much more detail!
Comment #8
wim leersComment #9
wim leers#7: oh YAY! 😄 Commits incoming that fully harden against this and inform the user — stay tuned, would love your reviews! 😊
Comment #10
wim leersThe bug has been fixed in the past two commits. Next: form-level validation to inform the end user.
Comment #11
wim leersValidation added:
.
This is ready for review. Unassigning.
Comment #12
longwaveWhile we are improving validation, do we need to consider the case where the tag contains a wildcard? I can imagine someone perhaps trying
<h*>to allow all heading tags...Comment #13
longwaveAlso, we need a functional test to cover the new error message.
Comment #14
wim leersFair!
Comment #15
wim leersDone in bfe30afe727adaf9434e63272ea53fb20726a3af, expanded to cover #12 in 336ad92cf3f0d14d27b73f9b2a9e66752fabe196.
Addressed #12 in c2b6acba05d76c1dba63c4777597ef7aff553a28.
Comment #16
wim leersWTF, d.o's gitlab integration shows 39e99a35 and c2b6acba, but not the commit in between the two: 336ad92. Which makes this issue infuriatingly difficult to interpret, since 336ad92 was supposed to fail! 🤐😫
Comment #17
longwaveOne question about the validation, other than that this looks great!
Comment #18
wim leersGreat catch! 👍
Fixed now 😊
Comment #19
longwaveLooks perfect - RTBC if bot agrees.
Also removing Markdown from the issue title :)
Comment #20
wim leersWe ran into this while working on the CKEditor 4 → CKEditor 5 migration path — so tagging .
#19: 😂🙈 — a consequence of being forced to work with GitLab and trying to make the most of its formatting idiosyncrasies…
Comment #21
longwaveI so wish we could just use Markdown in d.o comments, the backtick syntax is much quicker to write.
Comment #22
effulgentsia commentedif (strpos($tag, $star_protector) !== FALSE) {I like that this checks for a
*anywhere in the tag name, but the test addition inFilterHtmlTestonly covers<*>and<h*>. Let's add a case where there's more than one letter before and a case where there's one or more letters after. Perhaps those are less common to arise, but since people can enter whatever into the field, let's make sure we have predictable behavior for when they do.if (preg_match_all('/\<([a-z0-9]?\*)/', $allowed_html_value, $matches, PREG_SET_ORDER) > 0) {This regex covers any single alphanum before a *, but not more than one. For example, it would allow through
<su*>. Also, because it ends at the *, if you had<*frame>, it would then tell you that*is an unsupported wildcard tag rather than telling you that<*frame>is an unsupported wildcard tag. Since we also have the other validation, I suppose we can choose to not cover all of the same cases here, and focus this one on only the common errors, but in that case, perhaps we should make the regex just'/\<(h?\*)/'instead of any leading character? Though I wonder if it would be better to parse the tag names the same way we do it ingetHTMLRestrictions()so that we're validating the same way in both places?Comment #23
longwaveAdded a more complex test as per #22, improved the validation regex and also simplified the validation method a bit.
Comment #25
andregp commentedNeeds a reroll for 9.4

Diff doesn't apply for FilterAdminTest.php
That's because FilterAdminTest.php line 392 was changed from
$this->assertSession()->pageTextNotContains(t('The text format %format has been updated.', ['%format' => 'Basic HTML']));to
$this->assertSession()->pageTextNotContains("The text format Basic HTML has been updated.");right before the new code insertion, thus the fail.
But, after manually solving the diff conflict, the issue is indeed fixed. So, it only needs a reroll.


Comment #26
longwaveThank you for manually testing this. Just to let you know, there is no need to attach a screenshot of the failed patch, just commenting and tagging "needs reroll" is enough.
Rerolled for 9.4.x.
Comment #27
avpadernoComment #29
mpauloComment #30
ranjith_kumar_k_u commentedRerolled #26
Comment #31
longwaveComment #33
ranjith_kumar_k_u commentedTry to fix the test failure
Comment #34
ranjith_kumar_k_u commentedComment #36
wim leers#33 still applies cleanly to
10.1.xand matches the state of the MR. We should just close the MR IMHO?Let's find out if it still passes tests. I cannot RTBC this, I worked too much on it.
Comment #37
smustgrave commentedLet me know if I'm testing this wrong.
Tried
<*>but get this error with and without the patchInvalidArgumentException: The value for the special "*" global attribute HTML tag must be an array of attribute restrictions. in Drupal\ckeditor5\HTMLRestrictions::validateAllowedRestrictionsPhase2() (line 201 of core/modules/ckeditor5/src/HTMLRestrictions.php).
Tried
<data *>page saves with the page but doesn't save the value.Comment #39
pasqualleNeed reroll for 10.2
Comment #40
lawxen commentedJust a reroll of #33 for 10.2.x
Comment #41
lawxen commentedAfter reroll the patch and applied, but still couln't solve the problem of https://www.drupal.org/project/extended_html_filter/issues/3401513#comme...
Comment #43
andriy khomych commentedJust a reroll of MR for 10.6.x. But even with this https://www.drupal.org/project/extended_html_filter/issues/3401513#comme... is not working. IMHO, the best approach now is to use https://www.drupal.org/project/htmlpurifier and disable drupal core filter.
Comment #44
longwaveRerolled the MR against main.
Comment #45
smustgrave commentedThink maybe a bad rebase? Showing 1000+ changes.