Repository: oasis-open/cti-python-stix2
Pull request #438 (2020-07-25 18:50:16)

hack for issue_435
This is a bit of hack, adding to the original one.  Probably it would be better to change the grammar rule

objectPathComponent
  : <assoc=left> objectPathComponent objectPathComponent  # pathStep
  | '.' (IdentifierWithoutHyphen | StringLiteral)         # keyPathStep
  | LBRACK (IntPosLiteral|IntNegLiteral|ASTERISK) RBRACK  # indexPathStep
  ;

so "a[1]" is reduced to one object instead of two.


Patch:
diff --git a/stix2/pattern_visitor.py b/stix2/pattern_visitor.py
index c4deb64..a9d43c5 100644
--- a/stix2/pattern_visitor.py
+++ b/stix2/pattern_visitor.py
@@ -2,6 +2,7 @@
 
 import importlib
 import inspect
+from six import text_type
 
 from stix2patterns.exceptions import ParseException
 from stix2patterns.grammars.STIXPatternParser import TerminalNode
@@ -50,7 +51,7 @@ def check_for_valid_timetamp_syntax(timestamp_string):
 
 
 def same_boolean_operator(current_op, op_token):
-    return current_op == op_token.symbol.text
+    return current_op == op_token.getText()
 
 
 class STIXPatternVisitorForSTIX2():
@@ -259,6 +260,11 @@ class STIXPatternVisitorForSTIX2():
             if isinstance(next, TerminalNode):
                 property_path.append(self.instantiate("ListObjectPathComponent", current.property_name, next.getText()))
                 i += 2
+            elif isinstance(next, IntegerConstant):
+                property_path.append(self.instantiate("ListObjectPathComponent",
+                                                      current.property_name if isinstance(current, BasicObjectPathComponent) else text_type(current),
+                                                      next.value))
+                i += 2
             else:
                 property_path.append(current)
                 i += 1
@@ -272,7 +278,12 @@ class STIXPatternVisitorForSTIX2():
     # Visit a parse tree produced by STIXPatternParser#firstPathComponent.
     def visitFirstPathComponent(self, ctx):
         children = self.visitChildren(ctx)
-        step = children[0].getText()
+        first_component = children[0]
+        # hack for when the first component isn't a TerminalNode (see issue #438)
+        if isinstance(first_component, TerminalNode):
+            step = first_component.getText()
+        else:
+            step = text_type(first_component)
         # if step.endswith("_ref"):
         #     return stix2.ReferenceObjectPathComponent(step)
         # else:
@@ -291,8 +302,8 @@ class STIXPatternVisitorForSTIX2():
     def visitKeyPathStep(self, ctx):
         children = self.visitChildren(ctx)
         if isinstance(children[1], StringConstant):
-            # special case for hashes
-            return children[1].value
+            # special case for hashes and quoted steps
+            return children[1]
         else:
             return self.instantiate("BasicObjectPathComponent", children[1].getText(), True)
 
diff --git a/stix2/patterns.py b/stix2/patterns.py
index edcf017..bbee7ac 100644
--- a/stix2/patterns.py
+++ b/stix2/patterns.py
@@ -248,7 +248,10 @@ def make_constant(value):
 class _ObjectPathComponent(object):
     @staticmethod
     def create_ObjectPathComponent(component_name):
-        if component_name.endswith("_ref"):
+        # first case is to handle if component_name was quoted
+        if isinstance(component_name, StringConstant):
+            return BasicObjectPathComponent(component_name.value, False)
+        elif component_name.endswith("_ref"):
             return ReferenceObjectPathComponent(component_name)
         elif component_name.find("[") != -1:
             parse1 = component_name.split("[")


Test patch:
diff --git a/stix2/test/v20/test_pattern_expressions.py b/stix2/test/v20/test_pattern_expressions.py
index 0e0a9ca..fa9000e 100644
--- a/stix2/test/v20/test_pattern_expressions.py
+++ b/stix2/test/v20/test_pattern_expressions.py
@@ -512,15 +512,31 @@ def test_parsing_start_stop_qualified_expression():
 
 
 def test_parsing_mixed_boolean_expression_1():
-    patt_obj = create_pattern_object("[a:b = 1 AND a:b = 2 OR a:b = 3]",)
+    patt_obj = create_pattern_object("[a:b = 1 AND a:b = 2 OR a:b = 3]")
     assert str(patt_obj) == "[a:b = 1 AND a:b = 2 OR a:b = 3]"
 
 
 def test_parsing_mixed_boolean_expression_2():
-    patt_obj = create_pattern_object("[a:b = 1 OR a:b = 2 AND a:b = 3]",)
+    patt_obj = create_pattern_object("[a:b = 1 OR a:b = 2 AND a:b = 3]")
     assert str(patt_obj) == "[a:b = 1 OR a:b = 2 AND a:b = 3]"
 
 
+def test_parsing_integer_index():
+    patt_obj = create_pattern_object("[a:b[1]=2]")
+    assert str(patt_obj) == "[a:b[1] = 2]"
+
+
+# This should never occur, because the first component will always be a property_name, and they should not be quoted.
+def test_parsing_quoted_first_path_component():
+    patt_obj = create_pattern_object("[a:'b'[1]=2]")
+    assert str(patt_obj) == "[a:'b'[1] = 2]"
+
+
+def test_parsing_quoted_second_path_component():
+    patt_obj = create_pattern_object("[a:b.'b'[1]=2]")
+    assert str(patt_obj) == "[a:b.'b'[1] = 2]"
+
+
 def test_parsing_illegal_start_stop_qualified_expression():
     with pytest.raises(ValueError):
         create_pattern_object("[ipv4-addr:value = '1.2.3.4'] START '2016-06-01' STOP '2017-03-12T08:30:00Z'", version="2.0")
diff --git a/stix2/test/v21/test_pattern_expressions.py b/stix2/test/v21/test_pattern_expressions.py
index b574e05..3ba0aa6 100644
--- a/stix2/test/v21/test_pattern_expressions.py
+++ b/stix2/test/v21/test_pattern_expressions.py
@@ -654,6 +654,21 @@ def test_parsing_mixed_boolean_expression_2():
     assert str(patt_obj) == "[a:b = 1 OR a:b = 2 AND a:b = 3]"
 
 
+def test_parsing_integer_index():
+    patt_obj = create_pattern_object("[a:b[1]=2]")
+    assert str(patt_obj) == "[a:b[1] = 2]"
+
+# This should never occur, because the first component will always be a property_name, and they should not be quoted.
+def test_parsing_quoted_first_path_component():
+    patt_obj = create_pattern_object("[a:'b'[1]=2]")
+    assert str(patt_obj) == "[a:'b'[1] = 2]"
+
+
+def test_parsing_quoted_second_path_component():
+    patt_obj = create_pattern_object("[a:b.'b'[1]=2]")
+    assert str(patt_obj) == "[a:b.'b'[1] = 2]"
+
+
 def test_parsing_multiple_slashes_quotes():
     patt_obj = create_pattern_object("[ file:name = 'weird_name\\'' ]", version="2.1")
     assert str(patt_obj) == "[file:name = 'weird_name\\'']"