@@ -12,8 +12,9 @@ use thiserror::Error;
1212/// An error that occurs when invalid value is provided for the HTML comment text.
1313#[ derive( Error , Debug , Eq , PartialEq , Copy , Clone ) ]
1414pub enum CommentTextError {
15- /// The provided value contains the `-->` character sequence that preemptively closes the comment.
16- #[ error( "Comment text shouldn't contain comment closing sequence (`-->`)." ) ]
15+ /// The provided value contains a byte sequence that a WHATWG-conformant browser
16+ /// treats as a comment terminator (`-->`, `--!>`, a leading `>`, or a leading `->`).
17+ #[ error( "Comment text shouldn't contain a comment-closing sequence." ) ]
1718 CommentClosingSequence ,
1819
1920 /// The provided value contains a character that can't be represented in the document's [`encoding`].
@@ -36,6 +37,20 @@ pub struct Comment<'i> {
3637 user_data : Box < dyn Any > ,
3738}
3839
40+ /// Returns `true` if `text`, when emitted between `<!--` and `-->`, would let a
41+ /// WHATWG-conformant browser close the comment earlier than the trailing `-->`
42+ /// marker, leaking whatever follows as live markup.
43+ ///
44+ /// Four sequences trigger this:
45+ ///
46+ /// 1. `-->` anywhere in the body (comment-end state).
47+ /// 2. `--!>` anywhere in the body (comment-end-bang state).
48+ /// 3. A leading `>` (abrupt-closing-of-empty-comment from comment-start).
49+ /// 4. A leading `->` (abrupt-closing-of-empty-comment from comment-start-dash).
50+ fn contains_comment_closing_sequence ( text : & str ) -> bool {
51+ text. contains ( "-->" ) || text. contains ( "--!>" ) || text. starts_with ( '>' ) || text. starts_with ( "->" )
52+ }
53+
3954impl < ' i > Comment < ' i > {
4055 #[ inline]
4156 #[ must_use]
@@ -68,22 +83,22 @@ impl<'i> Comment<'i> {
6883 /// Sets the text of the comment.
6984 #[ inline]
7085 pub fn set_text ( & mut self , text : & str ) -> Result < ( ) , CommentTextError > {
71- if text. contains ( "-->" ) {
72- Err ( CommentTextError :: CommentClosingSequence )
73- } else {
74- // NOTE: if character can't be represented in the given
75- // encoding then encoding_rs replaces it with a numeric
76- // character reference. Character references are not
77- // supported in comments, so we need to bail.
78- match BytesCow :: owned_from_str_without_replacements ( text, self . encoding ) {
79- Ok ( text) => {
80- self . text = text;
81- self . raw . set_modified ( ) ;
82-
83- Ok ( ( ) )
84- }
85- Err ( _) => Err ( CommentTextError :: UnencodableCharacter ) ,
86+ if contains_comment_closing_sequence ( text) {
87+ return Err ( CommentTextError :: CommentClosingSequence ) ;
88+ }
89+
90+ // NOTE: if character can't be represented in the given
91+ // encoding then encoding_rs replaces it with a numeric
92+ // character reference. Character references are not
93+ // supported in comments, so we need to bail.
94+ match BytesCow :: owned_from_str_without_replacements ( text, self . encoding ) {
95+ Ok ( text) => {
96+ self . text = text;
97+ self . raw . set_modified ( ) ;
98+
99+ Ok ( ( ) )
86100 }
101+ Err ( _) => Err ( CommentTextError :: UnencodableCharacter ) ,
87102 }
88103 }
89104
@@ -314,11 +329,20 @@ mod tests {
314329
315330 #[ test]
316331 fn comment_closing_sequence_in_text ( ) {
317- rewrite_comment ( b"<!-- foo -->" , UTF_8 , |c| {
318- let err = c. set_text ( "foo -- bar --> baz" ) . unwrap_err ( ) ;
319-
320- assert_eq ! ( err, CommentTextError :: CommentClosingSequence ) ;
321- } ) ;
332+ // Every byte sequence here is treated as a comment terminator by a
333+ // WHATWG-conformant browser when emitted between `<!--` and `-->`.
334+ // See `contains_comment_closing_sequence`.
335+ for payload in [ "foo -- bar --> baz" , "foo --!> bar" , ">foo" , "->foo" ] {
336+ rewrite_comment ( b"<!-- foo -->" , UTF_8 , |c| {
337+ let err = c. set_text ( payload) . unwrap_err ( ) ;
338+
339+ assert_eq ! (
340+ err,
341+ CommentTextError :: CommentClosingSequence ,
342+ "expected `{payload}` to be rejected" ,
343+ ) ;
344+ } ) ;
345+ }
322346 }
323347
324348 #[ test]
0 commit comments