From e11da5cf91f26be91645b1b6e486a38f712bc182 Mon Sep 17 00:00:00 2001 From: eeshsaxena Date: Sat, 15 Aug 2026 13:04:21 +0530 Subject: [PATCH] Don't crash serializing an unknown rule with an unmatched closing brace do_CSSUnknownRule walks the rule's token sequence keeping a stack of open blocks, popping on each '}'. A '}' with no matching '{' (from a malformed at-rule) left the stack empty, so stacks.pop() raised IndexError while computing cssText. Only pop when the stack is non-empty; a stray '}' is then emitted as a plain token. --- cssutils/serialize.py | 6 ++++-- tests/test_cssunknownrule.py | 8 ++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/cssutils/serialize.py b/cssutils/serialize.py index b9765184..d692126e 100644 --- a/cssutils/serialize.py +++ b/cssutils/serialize.py @@ -720,8 +720,10 @@ def do_CSSUnknownRule(self, rule): for item in rule.seq: type_, val = item.type, item.value # PRE - if '}' == val: - # close last open item on stack + if '}' == val and stacks: + # close last open item on stack (a "}" with no matching + # "{" leaves the stack empty; fall through and emit it as a + # plain token instead of popping from an empty list) stackblock = stacks.pop().value() if stackblock: val = self._indentblock( diff --git a/tests/test_cssunknownrule.py b/tests/test_cssunknownrule.py index 8ecca842..e7001d5f 100644 --- a/tests/test_cssunknownrule.py +++ b/tests/test_cssunknownrule.py @@ -55,6 +55,14 @@ def test_init(self): assert '@init xxx {\n yyy\n }' == r.cssText assert r.wellformed + def test_serialize_unmatched_closing_brace(self): + "CSSUnknownRule serialization with a '}' that has no matching '{'" + # Serializing such a rule used to raise IndexError (pop from empty + # list) while walking the brace stack. + sheet = cssutils.parseString('@keyframes k { 0% { opacity: 0; }"}') + # must not raise + assert isinstance(sheet.cssText, bytes) + def test_cssText(self): "CSSUnknownRule.cssText" tests = {