Repository: oasis-open/cti-python-stix2

Issue:
Incorrect Pattern Error Propagation
When using `stix2/pattern_visitor.py/create_pattern_object()` with a pattern that does not include matching single quotes (e.g. "[ file: name = 'weirdname]" ), the stix2 library returns an unhelpful and unclear error. This is in comparison to the more useful error message provided by the pattern validator if the pattern was supplied to it directly. Thus, there should be a way for the error(s) generated by the pattern validator to be propagated back through stix2. See below stacktrace for more details.

(Based off of https://github.com/oasis-open/cti-python-stix2/issues/303)

![stacktrace](https://user-images.githubusercontent.com/43383927/73215354-f2b69680-4121-11ea-89ef-8d949202e2ed.png)


Discussion:

Patch:
diff --git a/stix2/pattern_visitor.py b/stix2/pattern_visitor.py
index b2d7a53..6ac3e98 100644
--- a/stix2/pattern_visitor.py
+++ b/stix2/pattern_visitor.py
@@ -1,16 +1,12 @@
 import importlib
 import inspect
 
-from antlr4 import CommonTokenStream, InputStream
-from antlr4.tree.Trees import Trees
-import six
 from stix2patterns.exceptions import ParseException
-from stix2patterns.grammars.STIXPatternLexer import STIXPatternLexer
 from stix2patterns.grammars.STIXPatternParser import (
     STIXPatternParser, TerminalNode,
 )
 from stix2patterns.grammars.STIXPatternVisitor import STIXPatternVisitor
-from stix2patterns.validator import STIXPatternErrorListener
+from stix2patterns.v20.pattern import Pattern
 
 from .patterns import *
 from .patterns import _BooleanExpression
@@ -328,41 +324,9 @@ class STIXPatternVisitorForSTIX2(STIXPatternVisitor):
 
 def create_pattern_object(pattern, module_suffix="", module_name=""):
     """
-    Validates a pattern against the STIX Pattern grammar.  Error messages are
-    returned in a list.  The test passed if the returned list is empty.
+    Create a STIX pattern AST from a pattern string.
     """
 
-    start = ''
-    if isinstance(pattern, six.string_types):
-        start = pattern[:2]
-        pattern = InputStream(pattern)
-
-    if not start:
-        start = pattern.readline()[:2]
-        pattern.seek(0)
-
-    parseErrListener = STIXPatternErrorListener()
-
-    lexer = STIXPatternLexer(pattern)
-    # it always adds a console listener by default... remove it.
-    lexer.removeErrorListeners()
-
-    stream = CommonTokenStream(lexer)
-
-    parser = STIXPatternParser(stream)
-
-    parser.buildParseTrees = True
-    # it always adds a console listener by default... remove it.
-    parser.removeErrorListeners()
-    parser.addErrorListener(parseErrListener)
-
-    # To improve error messages, replace "<INVALID>" in the literal
-    # names with symbolic names.  This is a hack, but seemed like
-    # the simplest workaround.
-    for i, lit_name in enumerate(parser.literalNames):
-        if lit_name == u"<INVALID>":
-            parser.literalNames[i] = parser.symbolicNames[i]
-
-    tree = parser.pattern()
+    pattern_obj = Pattern(pattern)
     builder = STIXPatternVisitorForSTIX2(module_suffix, module_name)
-    return builder.visit(tree)
+    return pattern_obj.visit(builder)


Test patch:
diff --git a/stix2/test/v21/test_indicator.py b/stix2/test/v21/test_indicator.py
index 152f253..23bad29 100644
--- a/stix2/test/v21/test_indicator.py
+++ b/stix2/test/v21/test_indicator.py
@@ -271,7 +271,7 @@ def test_indicator_stix20_invalid_pattern():
         )
 
     assert excinfo.value.cls == stix2.v21.Indicator
-    assert "FAIL: The same qualifier is used more than once" in str(excinfo.value)
+    assert "FAIL: Duplicate qualifier type encountered" in str(excinfo.value)
 
     ind = stix2.v21.Indicator(
         type="indicator",
diff --git a/stix2/test/v21/test_pattern_expressions.py b/stix2/test/v21/test_pattern_expressions.py
index 76880be..0c298f8 100644
--- a/stix2/test/v21/test_pattern_expressions.py
+++ b/stix2/test/v21/test_pattern_expressions.py
@@ -1,6 +1,7 @@
 import datetime
 
 import pytest
+from stix2patterns.exceptions import ParseException
 
 import stix2
 from stix2.pattern_visitor import create_pattern_object
@@ -515,3 +516,8 @@ def test_list_constant():
 def test_parsing_multiple_slashes_quotes():
     patt_obj = create_pattern_object("[ file:name = 'weird_name\\'' ]")
     assert str(patt_obj) == "[file:name = 'weird_name\\'']"
+
+
+def test_parse_error():
+    with pytest.raises(ParseException):
+        create_pattern_object("[ file: name = 'weirdname]")