diff --git a/stix2/pattern_visitor.py b/stix2/pattern_visitor.py index c4deb641..a9d43c50 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 @@ def visitObjectPath(self, ctx): 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 @@ def visitObjectType(self, ctx): # 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 @@ def visitPathStep(self, ctx): 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 edcf0170..bbee7ac8 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("[") diff --git a/stix2/test/v20/test_pattern_expressions.py b/stix2/test/v20/test_pattern_expressions.py index 0e0a9ca5..fa9000e0 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 b574e059..3ba0aa62 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\\'']"