Fix nginx parser crash on comments inside multi-line directives (#10598)

This commit is contained in:
Erik Morén
2026-05-23 08:41:32 +02:00
parent 750b9997de
commit af9273f9a4
2 changed files with 33 additions and 4 deletions
@@ -13,6 +13,7 @@ from typing import SupportsIndex
from typing import Union from typing import Union
from pyparsing import Combine from pyparsing import Combine
from pyparsing import FollowedBy
from pyparsing import Forward from pyparsing import Forward
from pyparsing import Group from pyparsing import Group
from pyparsing import Literal from pyparsing import Literal
@@ -50,15 +51,29 @@ class RawNginxParser:
token = paren_quote_extend | tokenchars | quoted token = paren_quote_extend | tokenchars | quoted
whitespace_token_group = space + token + ZeroOrMore(required_space + token) + space # An nginx comment runs from '#' to the end of the line. `restOfLine` does
assignment = whitespace_token_group + semicolon # not consume the trailing newline; the following `required_space` does.
comment = Literal('#') + restOfLine
# A separator between two tokens is whitespace, optionally interleaved with
# one or more comments. The `FollowedBy(token)` lookahead means a comment is
# only treated as inline when a real token follows it — otherwise we leave
# the `#` alone so existing fallback paths (e.g. comment-as-token in a block
# header) still parse. This lets a multi-line directive contain comment
# lines between its tokens (valid nginx), which the old grammar mis-parsed:
# an unbalanced `"` inside such a comment would be sucked up by the
# `multiline=True` quoted strings and run away across the rest of the file
# (issue #10598).
token_separator = required_space + ZeroOrMore(
comment + required_space + FollowedBy(token)
)
comment = space + Literal('#') + restOfLine whitespace_token_group = space + token + ZeroOrMore(token_separator + token) + space
assignment = whitespace_token_group + semicolon
block = Forward() block = Forward()
# order matters! see issue 518, and also http { # server { \n} # order matters! see issue 518, and also http { # server { \n}
contents = Group(comment) | Group(block) | Group(assignment) contents = Group(space + comment) | Group(block) | Group(assignment)
block_begin = Group(whitespace_token_group) block_begin = Group(whitespace_token_group)
block_innards = Group(ZeroOrMore(contents) + space).leave_whitespace() block_innards = Group(ZeroOrMore(contents) + space).leave_whitespace()
@@ -201,6 +201,20 @@ class TestRawNginxParser(unittest.TestCase):
[['#', ' server{']]] [['#', ' server{']]]
] ]
def test_comment_inside_multiline_directive(self):
# See https://github.com/certbot/certbot/issues/10598
# An unbalanced double-quote inside a comment that sits between tokens
# of a multi-line directive must not be sucked into a multiline quoted
# string and run away across the rest of the file.
source = ("http {\n"
" log_format json_test escape=json '{'\n"
" # '\"req\": \"$request\"' # GET \"/test?...\n"
" '}';\n"
"}\n")
parsed = loads(source)
# The config parses and round-trips byte-for-byte.
assert dumps(parsed) == source
def test_access_log(self): def test_access_log(self):
# see issue #3798 # see issue #3798
parsed = loads('access_log syslog:server=unix:/dev/log,facility=auth,' parsed = loads('access_log syslog:server=unix:/dev/log,facility=auth,'