Repository: certbot/certbot

Issue:
nginxparser does not recognize 'if' statements
E.g., this is unparseable by nginxparser:

```
if ($http_origin ~* ^https://www\.example\.com) {
   add_header      Access-Control-Allow-Origin "$http_origin";
}
```



Discussion:

Resolving patch:
diff --git a/letsencrypt_nginx/nginxparser.py b/letsencrypt_nginx/nginxparser.py
--- a/letsencrypt_nginx/nginxparser.py
+++ b/letsencrypt_nginx/nginxparser.py
@@ -3,7 +3,7 @@
 
 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)


Test patch:
diff --git a/letsencrypt_nginx/tests/nginxparser_test.py b/letsencrypt_nginx/tests/nginxparser_test.py
--- a/letsencrypt_nginx/tests/nginxparser_test.py
+++ b/letsencrypt_nginx/tests/nginxparser_test.py
@@ -84,6 +84,26 @@ def test_parse_from_file(self):
               ]]]]]
         )
 
+    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
--- /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;
+  }
+}