Repository: RedHatInsights/insights-core

Issue:
Bug in httpd conf parser 
The DocParser can't parse httpd conf files which contain the following lines:
```
RewriteCond %{HTTP:Accept-Encoding} \b(x-)?gzip\b
RedirectMatch ^\/?pulp_puppet\/forge\/[^\/]+\/[^\/]+\/(?!api\/v1\/releases\.json)(.*)$ /$1
RewriteCond %1%2 (^|&|;)([^(&|;)].*|$)
RewriteCond %{HTTP:Accept-Encoding} \b(x-)?gzip\b
<IfVersion < 2.4>
    Allow from all
  </IfVersion>
```



Discussion:
@csams, did you notice this issue? It is raised by our customers at BZ-1743066 since the parser can't parse the file, then the plugin returned wrong data. Could you help to fix this issue as soon as possible?
Another question is if parsers can't parse some files of the spec, can we make it return nothing other than just return the files it can parse and ignore the files it can't parse?

Resolving patch:
diff --git a/insights/combiners/httpd_conf.py b/insights/combiners/httpd_conf.py
--- a/insights/combiners/httpd_conf.py
+++ b/insights/combiners/httpd_conf.py
@@ -20,9 +20,9 @@
 from insights.core.plugins import combiner, parser
 from insights.parsr.query import (Directive, Entry, lift, lift2, Section,
         startswith)
-from insights.parsr import (Char, EOF, EOL, EndTagName, Forward, FS, GT, LT,
-        Letters, Lift, LineEnd, Many, Number, OneLineComment, PosMarker,
-        QuotedString, skip_none, StartTagName, String, WS, WSChar)
+from insights.parsr import (Char, EOF, EOL, EndTagName, Forward, FS, GT, InSet,
+        Literal, LT, Letters, Lift, LineEnd, Many, Number, OneLineComment,
+        PosMarker, QuotedString, skip_none, StartTagName, String, WS, WSChar)
 from insights.parsers.httpd_conf import HttpdConf, dict_deep_merge, ParsedData
 from insights.specs import Specs
 from insights.util import deprecated
@@ -166,8 +166,7 @@ def _deep_search(data, dr, sc):
                 section = (section, '')
             elif isinstance(section, tuple) and len(section) == 1:
                 section = (section[0], '')
-            elif (not isinstance(section, tuple) or
-                    (len(section) == 0 or len(section) > 2)):
+            elif (not isinstance(section, tuple) or (len(section) == 0 or len(section) > 2)):
                 return []
             return _deep_search(self.data, directive, section)
 
@@ -273,21 +272,27 @@ class DocParser(object):
     def __init__(self, ctx):
         self.ctx = ctx
 
-        name_chars = string.ascii_letters + "_/"
-        Name = String(name_chars)
         Complex = Forward()
+        Comment = (WS >> OneLineComment("#")).map(lambda x: None)
+
+        Name = String(string.ascii_letters + "_/")
         Num = Number & (WSChar | LineEnd)
-        Cont = Char("\\") + EOL
+
         StartName = WS >> PosMarker(StartTagName(Letters)) << WS
         EndName = WS >> EndTagName(Letters, ignore_case=True) << WS
-        Comment = (WS >> OneLineComment("#")).map(lambda x: None)
+
+        Cont = Char("\\") + EOL
         AttrStart = Many(WSChar)
         AttrEnd = (Many(WSChar) + Cont) | Many(WSChar)
-        BareAttr = String(set(string.printable) - (set(string.whitespace) | set(";{}<>\\'\"")))
-        Attr = AttrStart >> (Num | BareAttr | QuotedString) << AttrEnd
+
+        OpAttr = (Literal("!=") | Literal("<=") | Literal(">=") | InSet("<>")) & WSChar
+        BareAttr = String(set(string.printable) - (set(string.whitespace) | set("<>'\"")))
+        Attr = AttrStart >> (Num | QuotedString | OpAttr | BareAttr) << AttrEnd
         Attrs = Many(Attr)
+
         StartTag = (WS + LT) >> (StartName + Attrs) << (GT + WS)
         EndTag = (WS + LT + FS) >> EndName << (GT + WS)
+
         Simple = WS >> (Lift(self.to_directive) * PosMarker(Name) * Attrs) << WS
         Stanza = Simple | Complex | Comment | Many(WSChar | EOL, lower=1).map(lambda x: None)
         Complex <= (Lift(self.to_section) * StartTag * Many(Stanza).map(skip_none)) << EndTag


Test patch:
diff --git a/insights/combiners/tests/test_httpd_conf_tree.py b/insights/combiners/tests/test_httpd_conf_tree.py
--- a/insights/combiners/tests/test_httpd_conf_tree.py
+++ b/insights/combiners/tests/test_httpd_conf_tree.py
@@ -385,6 +385,17 @@
 """.strip()
 
 
+HTTPD_REGEX_AND_OP_ATTRS = r"""
+RewriteCond %{HTTP:Accept-Encoding} \b(x-)?gzip\b
+RedirectMatch ^\/?pulp_puppet\/forge\/[^\/]+\/[^\/]+\/(?!api\/v1\/releases\.json)(.*)$ /$1
+RewriteCond %1%2 (^|&|;)([^(&|;)].*|$)
+RewriteCond %{HTTP:Accept-Encoding} \b(x-)?gzip\b
+<IfVersion < 2.4>
+    Allow from all
+  </IfVersion>
+""".strip()
+
+
 HTTPD_EMBEDDED_QUOTES = r"""
 # DirectoryIndex: sets the file that Apache will serve if a directory
 # is requested.
@@ -722,3 +733,14 @@ def test_indented_lines_and_comments():
 
     request_headers = result['RequestHeader']
     assert len(request_headers) == 2
+
+
+def test_regex_and_op_attrs():
+    httpd = _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