Permissive parsing of nginx map blocks
diff --git a/certbot-compatibility-test/nginx/nginx-roundtrip-testdata/anothermapcase/nginx.conf b/certbot-compatibility-test/nginx/nginx-roundtrip-testdata/anothermapcase/nginx.conf
new file mode 100644
index 00000000000..b3ca02f929c
--- /dev/null
+++ b/certbot-compatibility-test/nginx/nginx-roundtrip-testdata/anothermapcase/nginx.conf
@@ -0,0 +1,3 @@
+map $uri $blogname{
+    ~^(?P<blogpath>/[^/]+/)files/(.*)       $blogpath ;
+}
diff --git a/certbot-nginx/certbot_nginx/nginxparser.py b/certbot-nginx/certbot_nginx/nginxparser.py
index d6c352296f4..1859777d869 100644
--- a/certbot-nginx/certbot_nginx/nginxparser.py
+++ b/certbot-nginx/certbot_nginx/nginxparser.py
@@ -23,10 +23,12 @@ class RawNginxParser(object):
     right_bracket = space.leaveWhitespace() + Literal("}").suppress()
     semicolon = Literal(";").suppress()
     key = Word(alphanums + "_/+-.")
-    dollar_var = Combine(Literal('$') + nonspace)
+    dollar_var = Combine(Literal('$') + Regex(r"[^\{\};,\s]+"))
     condition = Regex(r"\(.+\)")
     # Matches anything that is not a special character AND any chars in single
     # or double quotes
+    # All of these COULD be upgraded to something like
+    # https://stackoverflow.com/a/16130746
     value = Regex(r"((\".*\")?(\'.*\')?[^\{\};,]?)+")
     location = CharsNotIn("{};," + string.whitespace)
     # modifier for location uri [ = | ~ | ~* | ^~ ]
@@ -38,19 +40,35 @@ class RawNginxParser(object):
     assignment = space + key + Optional(space + value, default=None) + semicolon
     location_statement = space + Optional(modifier) + Optional(space + location + space)
     if_statement = space + Literal("if") + space + condition + space
+
     map_statement = space + Literal("map") + space + nonspace + space + dollar_var + space
-    block = Forward()
+    # This is NOT an accurate way to parse nginx map entries; it's almost
+    # certianly too permissive and may be wrong in other ways, but it should
+    # preserve things correctly in mmmmost or all cases.
+    #
+    #    - I can neither prove nor disprove that it is corect wrt all escaped
+    #      semicolon situations
+    # Addresses https://github.com/fatiherikli/nginxparser/issues/19
+    map_pattern = Regex(r'".*"') | Regex(r"'.*'") | nonspace
+    map_entry = space + map_pattern + space + value + space + semicolon
+    map_block = Group(
+        # key could for instance be "server" or "http", or "location" (in which case
+        # location_statement needs to have a non-empty location)
+        Group(map_statement).leaveWhitespace() +
+        left_bracket +
+        Group(ZeroOrMore(Group(comment | map_entry)) + space).leaveWhitespace() +
+        right_bracket)
 
+    block = Forward()
     block << Group(
         # key could for instance be "server" or "http", or "location" (in which case
         # location_statement needs to have a non-empty location)
-        (Group(space + key + location_statement) ^ Group(if_statement) ^
-        Group(map_statement)).leaveWhitespace() +
+        (Group(space + key + location_statement) ^ Group(if_statement)).leaveWhitespace() +
         left_bracket +
-        Group(ZeroOrMore(Group(comment | assignment) | block) + space).leaveWhitespace() +
-        right_bracket)
+        Group(ZeroOrMore(Group(comment | assignment) | block | map_block) + space).leaveWhitespace()
+              + right_bracket)
 
-    script = OneOrMore(Group(comment | assignment) ^ block) + space + stringEnd
+    script = OneOrMore(Group(comment | assignment) ^ block ^ map_block) + space + stringEnd
     script.parseWithTabs()
 
     def __init__(self, source):

Closes #3206, at least in a good-enough-for-now sense.
