diff --git a/letsencrypt_nginx/nginxparser.py b/letsencrypt_nginx/nginxparser.py index 18ba8b0bd..f24455d59 100644 --- a/letsencrypt_nginx/nginxparser.py +++ b/letsencrypt_nginx/nginxparser.py @@ -3,7 +3,7 @@ import string from pyparsing import ( Literal, White, Word, alphanums, CharsNotIn, Forward, Group, - Optional, OneOrMore, ZeroOrMore, pythonStyleComment) + Optional, OneOrMore, Regex, ZeroOrMore, pythonStyleComment) class RawNginxParser(object): @@ -16,17 +16,21 @@ class RawNginxParser(object): semicolon = Literal(";").suppress() space = White().suppress() key = Word(alphanums + "_/") - value = CharsNotIn("{};,") + # Matches anything that is not a special character AND any chars in single + # or double quotes + value = Regex(r"((\".*\")?(\'.*\')?[^\{\};,]?)+") location = CharsNotIn("{};," + string.whitespace) # modifier for location uri [ = | ~ | ~* | ^~ ] modifier = Literal("=") | Literal("~*") | Literal("~") | Literal("^~") # rules assignment = (key + Optional(space + value) + semicolon) + location_statement = Optional(space + modifier) + Optional(space + location) + if_statement = Literal("if") + space + Regex(r"\(.+\)") + space block = Forward() block << Group( - Group(key + Optional(space + modifier) + Optional(space + location)) + (Group(key + location_statement) ^ Group(if_statement)) + left_bracket + Group(ZeroOrMore(Group(assignment) | block)) + right_bracket) diff --git a/letsencrypt_nginx/tests/nginxparser_test.py b/letsencrypt_nginx/tests/nginxparser_test.py index 23f46c72a..73a89534b 100644 --- a/letsencrypt_nginx/tests/nginxparser_test.py +++ b/letsencrypt_nginx/tests/nginxparser_test.py @@ -84,6 +84,26 @@ class TestRawNginxParser(unittest.TestCase): ]]]]] ) + def test_parse_from_file2(self): + parsed = load(open(util.get_data_filename('edge_cases.conf'))) + self.assertEqual( + parsed, + [[['server'], [['server_name', 'simple']]], + [['server'], + [['server_name', 'with.if'], + [['location', '~', '^/services/.+$'], + [[['if', '($request_filename ~* \\.(ttf|woff)$)'], + [['add_header', 'Access-Control-Allow-Origin "*"']]]]]]], + [['server'], + [['server_name', 'with.complicated.headers'], + [['location', '~*', '\\.(?:gif|jpe?g|png)$'], + [['add_header', 'Pragma public'], + ['add_header', + 'Cache-Control \'public, must-revalidate, proxy-revalidate\'' + ' "test,;{}" foo'], + ['blah', '"hello;world"'], + ['try_files', '$uri @rewrites']]]]]]) + def test_dump_as_file(self): parsed = load(open(util.get_data_filename('nginx.conf'))) parsed[-1][-1].append([['server'], diff --git a/letsencrypt_nginx/tests/testdata/etc_nginx/edge_cases.conf b/letsencrypt_nginx/tests/testdata/etc_nginx/edge_cases.conf new file mode 100644 index 000000000..477cb1c45 --- /dev/null +++ b/letsencrypt_nginx/tests/testdata/etc_nginx/edge_cases.conf @@ -0,0 +1,27 @@ +# This is not a valid nginx config file but it tests edge cases in valid nginx syntax + +server { + server_name simple; +} + +server { + server_name with.if; + location ~ ^/services/.+$ { + if ($request_filename ~* \.(ttf|woff)$) { + add_header Access-Control-Allow-Origin "*"; + } + } +} + +server { + server_name with.complicated.headers; + + location ~* \.(?:gif|jpe?g|png)$ { + + add_header Pragma public; + add_header Cache-Control 'public, must-revalidate, proxy-revalidate' "test,;{}" foo; + blah "hello;world"; + + try_files $uri @rewrites; + } +}