Skip to content

html: escape text that would close its containing raw text element - #255

Open
adilburaksen wants to merge 1 commit into
golang:masterfrom
adilburaksen:html-escape-raw-text-end-tag
Open

html: escape text that would close its containing raw text element#255
adilburaksen wants to merge 1 commit into
golang:masterfrom
adilburaksen:html-escape-raw-text-end-tag

Conversation

@adilburaksen

@adilburaksen adilburaksen commented Aug 2, 2026

Copy link
Copy Markdown

childTextNodesAreLiteral reports that the text children of script, style,
xmp, iframe, noembed, noframes, noscript and plaintext are to be written
out literally. That is correct for text produced by the tokenizer in raw
text mode, which cannot contain an end tag for its own element: the
tokenizer would have closed the element rather than produce it.

noscript is different. When a document is parsed with scripting disabled,
the parser descends into noscript and decodes character references in its
contents, so the resulting text node can hold arbitrary markup, including
"". Rendering that literally lets the text close the element
and be re-parsed as live nodes:

in:  <body><noscript>&lt;/noscript&gt;&lt;img src=x&gt;</noscript>
out: <body><noscript></noscript><img src=x></noscript></body>

The parse tree contains no img element; the rendered output does. Callers
that parse untrusted HTML, decide what is safe by walking the tree, and
re-serialize with Render -- the sequence the package documentation
recommends for security contexts -- emit an element they never approved.

Escape a literal text child when it would otherwise close its containing
element. The predicate mirrors the tokenizer's readRawEndTag: "</" plus
the tag name matched ASCII case-insensitively, followed by a tag-name
terminator. For text produced by the tokenizer this never triggers, so
rendering is unchanged.

plaintext and script are excluded. plaintext has no end tag; it consumes
the rest of the input, so no text can close it. script follows the script
data escaping rules in readScript, under which an embedded "</script>"
does not always close the element, so its text can legitimately contain
one and must still be written literally -- html5lib-tests tests16.dat
case 168 covers this.

@gopherbot

Copy link
Copy Markdown
Contributor

This PR (HEAD: 1367502) has been imported to Gerrit for code review.

Please visit Gerrit at https://go-review.googlesource.com/c/net/+/809240.

Important tips:

  • Don't comment on this PR. All discussion takes place in Gerrit.
  • You need a Gmail or other Google account to log in to Gerrit.
  • To change your code in response to feedback:
    • Push a new commit to the branch used by your GitHub PR.
    • A new "patch set" will then appear in Gerrit.
    • Respond to each comment by marking as Done in Gerrit if implemented as suggested. You can alternatively write a reply.
    • Critical: you must click the blue Reply button near the top to publish your Gerrit responses.
    • Multiple commits in the PR will be squashed by GerritBot.
  • The title and description of the GitHub PR are used to construct the final commit message.
    • Edit these as needed via the GitHub web interface (not via Gerrit or git).
    • You should word wrap the PR description at ~76 characters unless you need longer lines (e.g., for tables or URLs).
  • See the Sending a change via GitHub and Reviews sections of the Contribution Guide as well as the FAQ for details.

childTextNodesAreLiteral reports that the text children of script, style,
xmp, iframe, noembed, noframes, noscript and plaintext are to be written
out literally. That is correct for text produced by the tokenizer in raw
text mode, which cannot contain an end tag for its own element: the
tokenizer would have closed the element rather than produce it.

noscript is different. When a document is parsed with scripting disabled,
the parser descends into noscript and decodes character references in its
contents, so the resulting text node can hold arbitrary markup, including
"</noscript>". Rendering that literally lets the text close the element
and be re-parsed as live nodes:

	in:  <body><noscript>&lt;/noscript&gt;&lt;img src=x&gt;</noscript>
	out: <body><noscript></noscript><img src=x></noscript></body>

The parse tree contains no img element; the rendered output does. Callers
that parse untrusted HTML, decide what is safe by walking the tree, and
re-serialize with Render -- the sequence the package documentation
recommends for security contexts -- emit an element they never approved.

Escape a literal text child when it would otherwise close its containing
element. The predicate mirrors the tokenizer's readRawEndTag: "</" plus
the tag name matched ASCII case-insensitively, followed by a tag-name
terminator. For text produced by the tokenizer this never triggers, so
rendering is unchanged.

plaintext and script are excluded. plaintext has no end tag; it consumes
the rest of the input, so no text can close it. script follows the script
data escaping rules in readScript, under which an embedded "</script>"
does not always close the element, so its text can legitimately contain
one and must still be written literally -- html5lib-tests tests16.dat
case 168 covers this.
@adilburaksen
adilburaksen force-pushed the html-escape-raw-text-end-tag branch from 1367502 to 488eb41 Compare August 2, 2026 19:44
@gopherbot

Copy link
Copy Markdown
Contributor

Message from Gopher Robot:

Patch Set 1:

(1 comment)


Please don’t reply on this GitHub thread. Visit golang.org/cl/809240.
After addressing review feedback, remember to publish your drafts!

@gopherbot

Copy link
Copy Markdown
Contributor

This PR (HEAD: 488eb41) has been imported to Gerrit for code review.

Please visit Gerrit at https://go-review.googlesource.com/c/net/+/809240.

Important tips:

  • Don't comment on this PR. All discussion takes place in Gerrit.
  • You need a Gmail or other Google account to log in to Gerrit.
  • To change your code in response to feedback:
    • Push a new commit to the branch used by your GitHub PR.
    • A new "patch set" will then appear in Gerrit.
    • Respond to each comment by marking as Done in Gerrit if implemented as suggested. You can alternatively write a reply.
    • Critical: you must click the blue Reply button near the top to publish your Gerrit responses.
    • Multiple commits in the PR will be squashed by GerritBot.
  • The title and description of the GitHub PR are used to construct the final commit message.
    • Edit these as needed via the GitHub web interface (not via Gerrit or git).
    • You should word wrap the PR description at ~76 characters unless you need longer lines (e.g., for tables or URLs).
  • See the Sending a change via GitHub and Reviews sections of the Contribution Guide as well as the FAQ for details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants