Skip to content

Commit

Permalink
fix: Multiline quote parsing of httpd conf files (#3392)
Browse files Browse the repository at this point in the history
* The parsr logic didn't handle line continuations properly, and didn't
  handle multiline quoted directives either. I fixed the logic and added
  in tests.

Signed-off-by: Ryan Blakley <[email protected]>
  • Loading branch information
ryan-blakley authored Apr 20, 2022
1 parent e7cd300 commit 2864da3
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 15 deletions.
46 changes: 35 additions & 11 deletions insights/combiners/tests/test_httpd_conf_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,8 @@
SetEnv GIT_HTTP_EXPORT_ALL
DocumentRoot /var/www
# ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
ProxyPass "http://backend.example.com/a-long-path-to-demonstrate" \
connectiontimeout=1 retry=0
ScriptAliasMatch \
"(?x)^/git/(.*/(HEAD | \
info/refs | \
Expand All @@ -521,6 +523,10 @@
</VirtualHost>
"""

INVALID_CONF = """
<<<>>>><>KDLKJLDSF><SDNF<KNSD><FN
""".strip()


def test_mixed_case_tags():
httpd = httpd_conf.HttpdConf(context_wrap(HTTPD_CONF_MIXED, path='/etc/httpd/conf/httpd.conf'))
Expand Down Expand Up @@ -857,14 +863,32 @@ def test_empty_attr():

def test_multiline_quote():
with pytest.raises(ParseException):
httpd_conf.HttpdConf(context_wrap(HTTPD_MULTILINE_QUOTE, path='/etc/httpd/conf/error.conf'))

httpd = httpd_conf.HttpdConf(context_wrap(HTTPD_REGEX_AND_OP_ATTRS, path='/etc/httpd/conf/httpd.conf'))
result = HttpdConfTree([httpd])

rewrite_cond = result["RewriteCond"]
assert len(rewrite_cond) == 3

if_version = result["IfVersion"]
assert len(if_version) == 1
assert if_version.value == "< 2.4"
httpd_conf.HttpdConf(context_wrap(INVALID_CONF, path='/etc/httpd/conf/error.conf'))

ml_quote = httpd_conf.HttpdConf(context_wrap(HTTPD_MULTILINE_QUOTE, path='/etc/httpd/conf.d/multiline-quote.conf'))
httpd = httpd_conf.HttpdConf(context_wrap(HTTPD_CONF_1, path='/etc/httpd/conf/httpd.conf'))
result = HttpdConfTree([httpd, ml_quote])

assert result['JustFotTest_NoSec'].value == "/var/www/cgi"
assert result['IfModule']['ServerLimit'].value == 256
assert result['Listen'].value == 81

# For the below tests, check the string value, then check the actual
# attrs since quotes from quoted text are removed. The attrs store the
# actual parsed attrs in a list form.
assert result['VirtualHost']['SetEnv'].values == ['GIT_PROJECT_ROOT /var/www/git', 'GIT_HTTP_EXPORT_ALL']
assert result['VirtualHost']['SetEnv'].children[0].attrs == ['GIT_PROJECT_ROOT', '/var/www/git']
assert result['VirtualHost']['SetEnv'].children[1].attrs == ['GIT_HTTP_EXPORT_ALL']

assert result['VirtualHost']['ProxyPass'].value == "http://backend.example.com/a-long-path-to-demonstrate connectiontimeout=1 retry=0" # noqa
assert result['VirtualHost']['ProxyPass'].children[-1].attrs == [
'http://backend.example.com/a-long-path-to-demonstrate',
'connectiontimeout=1',
'retry=0'
]

assert result['VirtualHost']['ScriptAliasMatch'].value == r"(?x)^/git/(.*/(HEAD | info/refs | objects/(info/[^/]+ | [0-9a-f]{2}/[0-9a-f]{38} | pack/pack-[0-9a-f]{40}\.(pack|idx)) | git-(upload|receive)-pack))$ /usr/libexec/git-core/git-http-backend/$1" # noqa
assert result['VirtualHost']['ScriptAliasMatch'].children[-1].attrs == [
r'(?x)^/git/(.*/(HEAD | info/refs | objects/(info/[^/]+ | [0-9a-f]{2}/[0-9a-f]{38} | pack/pack-[0-9a-f]{40}\.(pack|idx)) | git-(upload|receive)-pack))$', # noqa
'/usr/libexec/git-core/git-http-backend/$1'
]
12 changes: 8 additions & 4 deletions insights/parsers/httpd_conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,24 @@ def __init__(self, ctx):
Complex = Forward()
Comment = (WS >> OneLineComment("#")).map(lambda x: None)

Cont = Char("\\") + EOL
First = InSet(string.ascii_letters + "_/")
Rest = String(string.ascii_letters + "_/" + string.digits)
Name = (First + Rest).map("".join)
FirstRest = (First + Rest).map("".join)
Name = (FirstRest << (Many(WSChar) + Cont)) | FirstRest

Num = Number & (WSChar | LineEnd)

StartName = WS >> PosMarker(StartTagName(Letters)) << WS
EndName = WS >> EndTagName(Letters, ignore_case=True) << WS

Cont = Char("\\") + EOL
AttrStart = Many(WSChar)
AttrEnd = (Many(WSChar) + Cont) | Many(WSChar)

BareAttr = String(set(string.printable) - (set(string.whitespace) | set("<>'\"")))
OpAttr = (Literal("!=") | Literal("<=") | Literal(">=") | InSet("<>")) & WSChar + BareAttr
EmptyAttr = String('"\'', min_length=2)
Attr = AttrStart >> (Num | QuotedString | OpAttr | BareAttr | EmptyAttr) << AttrEnd
Attr = AttrStart >> (Num | QuotedString.map(self.remove_cont) | OpAttr | BareAttr | EmptyAttr) << AttrEnd
Attrs = Many(Attr)

StartTag = (WS + LT) >> (StartName + Attrs) << (GT + WS)
Expand All @@ -51,6 +52,9 @@ def __init__(self, ctx):

self.Top = Doc + EOF

def remove_cont(self, val):
return "".join([x.strip().strip("\\") for x in val.split("\n")])

def typed(self, val):
try:
v = val.lower()
Expand All @@ -77,7 +81,7 @@ def __call__(self, content):
try:
return self.Top(content)
except Exception:
raise ParseException("There was an exception when parsing the config file.")
raise ParseException("There was an exception when parsing one of the httpd config files.")


class HttpdConfBase(ConfigParser):
Expand Down

0 comments on commit 2864da3

Please sign in to comment.