Commit dc49225 for mammothjs

commit dc49225425c2c07de0a6dc3529f2386c82a032b4
Author: Michael Williamson <mike@zwobble.org>
Date:   Sat Sep 12 09:29:13 2026 +0100

    Avoid excessive backtracking when parsing unterminated strings

diff --git a/NEWS b/NEWS
index 1206dac..77f348b 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,12 @@
+# 1.12.3
+
+* Avoid excessive backtracking when parsing an unterminated string with many
+  escape sequences. The previous behaviour would allow maliciously crafted
+  documents to cause a denial of service.
+
+  Note that it is still strongly recommended to process untrusted documents in
+  a separate thread with a timeout to avoid potential similar issues.
+
 # 1.12.2

 * Avoid prototype pollution when reading the styles defined in a document. This
diff --git a/lib/styles/parser/tokeniser.js b/lib/styles/parser/tokeniser.js
index fa45ff9..ef2a2c3 100644
--- a/lib/styles/parser/tokeniser.js
+++ b/lib/styles/parser/tokeniser.js
@@ -3,7 +3,7 @@ var RegexTokeniser = lop.RegexTokeniser;

 exports.tokenise = tokenise;

-var stringPrefix = "'((?:\\\\.|[^'])*)";
+var stringPrefix = "'((?:\\\\(?:.|$)|[^'\\\\])*)";

 function tokenise(string) {
     var identifierCharacter = "(?:[a-zA-Z\\-_]|\\\\.)";
diff --git a/test/styles/parser/tokeniser.tests.js b/test/styles/parser/tokeniser.tests.js
index 16f3847..04c25e0 100644
--- a/test/styles/parser/tokeniser.tests.js
+++ b/test/styles/parser/tokeniser.tests.js
@@ -31,10 +31,30 @@ test("strings are tokenised", function() {
     assertTokens("'Tristan'", [isToken("string", "Tristan")]);
 });

+test("escaped string terminators in strings are tokenised", function() {
+    assertTokens("'Tristan\\''", [isToken("string", "Tristan\\'")]);
+});
+
+test("escape sequences in strings are tokenised", function() {
+    assertTokens("'Tristan\\\\'", [isToken("string", "Tristan\\\\")]);
+});
+
 test("unterminated strings are tokenised", function() {
     assertTokens("'Tristan", [isToken("unterminated-string", "Tristan")]);
 });

+test("unterminated strings ending with escaped string terminator are tokenised", function() {
+    assertTokens("'Tristan\\'", [isToken("unterminated-string", "Tristan\\'")]);
+});
+
+test("unterminated strings with unterminated escape are tokenised", function() {
+    assertTokens("'Tristan\\", [isToken("unterminated-string", "Tristan\\")]);
+});
+
+test("unterminated strings with many escape sequences do not cause excessive backtracking", function() {
+    assertTokens("'" + "\\a".repeat(50), [isToken("unterminated-string", "\\a".repeat(50))]);
+});
+
 test("arrows are tokenised", function() {
     assertTokens("=>", [isToken("arrow")]);
 });