diff options
| -rw-r--r-- | NEWS | 9 | ||||
| -rw-r--r-- | mammoth/styles/parser/tokeniser.py | 8 | ||||
| -rw-r--r-- | tests/styles/parser/tokeniser_tests.py | 14 |
3 files changed, 26 insertions, 5 deletions
@@ -1,3 +1,12 @@ +# 1.12.2 + +* 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.1 * Fix: on Windows, when an image's content type includes a backslash in the diff --git a/mammoth/styles/parser/tokeniser.py b/mammoth/styles/parser/tokeniser.py index 7366cfe..96c537e 100644 --- a/mammoth/styles/parser/tokeniser.py +++ b/mammoth/styles/parser/tokeniser.py @@ -13,13 +13,13 @@ class TokenType(object): UNTERMINATED_STRING = "unterminated string" INTEGER = "integer" END = "end" - + def regex_tokeniser(rules): rules = [(token_type, _to_regex(regex)) for token_type, regex in rules] rules.append(("unknown", re.compile("."))) - + def tokenise(value): tokens = [] index = 0 @@ -39,7 +39,7 @@ def regex_tokeniser(rules): return tokens return tokenise - + def _to_regex(value): if hasattr(value, "match"): @@ -48,7 +48,7 @@ def _to_regex(value): return re.compile(value) -_string_prefix = r"'(?:\\.|[^'])*" +_string_prefix = r"'(?:\\(?:.|$)|[^'\\])*" _identifier_character = r"(?:[a-zA-Z\-_]|\\.)" tokenise = regex_tokeniser([ diff --git a/tests/styles/parser/tokeniser_tests.py b/tests/styles/parser/tokeniser_tests.py index 482c5b0..9fce077 100644 --- a/tests/styles/parser/tokeniser_tests.py +++ b/tests/styles/parser/tokeniser_tests.py @@ -31,14 +31,26 @@ def test_strings_are_tokenised(): assert_tokens("'Tristan'", is_token("string", "'Tristan'")) -def test_escape_sequences_in_strings_are_tokenised(): +def test_escaped_string_terminators_in_strings_are_tokenised(): assert_tokens(r"'Tristan\''", is_token("string", r"'Tristan\''")) +def test_escape_sequences_in_strings_are_tokenised(): + assert_tokens(r"'Tristan\\'", is_token("string", r"'Tristan\\'")) + + def test_unterminated_strings_are_tokenised(): assert_tokens("'Tristan", is_token("unterminated string", "'Tristan")) +def test_unterminated_strings_with_unterminated_escape_are_tokenised(): + assert_tokens("'Tristan\\", is_token("unterminated string", "'Tristan\\")) + + +def test_unterminated_strings_with_many_escape_sequences_do_not_cause_excessive_backtracking(): + assert_tokens("'" + "\\a" * 50, is_token("unterminated string", "'" + "\\a" * 50)) + + def test_arrows_are_tokenised(): assert_tokens("=>=>", is_token("symbol", "=>"), is_token("symbol", "=>")) |
