diff --git a/config/dpkg/changelog b/config/dpkg/changelog index a4a267e4a6..abecb0c1dd 100644 --- a/config/dpkg/changelog +++ b/config/dpkg/changelog @@ -2,4 +2,4 @@ python-plaso (1.3.1-1) unstable; urgency=low * Auto-generated - -- Log2Timeline Sat, 05 Sep 2015 19:14:12 +0200 + -- Log2Timeline Sat, 05 Sep 2015 19:18:41 +0200 diff --git a/docs/plaso.filters.rst b/docs/plaso.filters.rst index 92c669b4a7..4063043dca 100644 --- a/docs/plaso.filters.rst +++ b/docs/plaso.filters.rst @@ -12,18 +12,34 @@ plaso.filters.dynamic_filter module :undoc-members: :show-inheritance: -plaso.filters.eventfilter module +plaso.filters.event_filter module +--------------------------------- + +.. automodule:: plaso.filters.event_filter + :members: + :undoc-members: + :show-inheritance: + +plaso.filters.filter_list module -------------------------------- -.. automodule:: plaso.filters.eventfilter +.. automodule:: plaso.filters.filter_list + :members: + :undoc-members: + :show-inheritance: + +plaso.filters.interface module +------------------------------ + +.. automodule:: plaso.filters.interface :members: :undoc-members: :show-inheritance: -plaso.filters.filterlist module -------------------------------- +plaso.filters.manager module +---------------------------- -.. automodule:: plaso.filters.filterlist +.. automodule:: plaso.filters.manager :members: :undoc-members: :show-inheritance: diff --git a/docs/plaso.lib.rst b/docs/plaso.lib.rst index 74b6292348..54cb3b5174 100644 --- a/docs/plaso.lib.rst +++ b/docs/plaso.lib.rst @@ -52,14 +52,6 @@ plaso.lib.eventdata module :undoc-members: :show-inheritance: -plaso.lib.filter_interface module ---------------------------------- - -.. automodule:: plaso.lib.filter_interface - :members: - :undoc-members: - :show-inheritance: - plaso.lib.lexer module ---------------------- @@ -108,14 +100,6 @@ plaso.lib.py2to3 module :undoc-members: :show-inheritance: -plaso.lib.registry module -------------------------- - -.. automodule:: plaso.lib.registry - :members: - :undoc-members: - :show-inheritance: - plaso.lib.specification module ------------------------------ diff --git a/plaso/analysis/browser_search.py b/plaso/analysis/browser_search.py index 4346f5f12b..ac1e134f5c 100644 --- a/plaso/analysis/browser_search.py +++ b/plaso/analysis/browser_search.py @@ -5,9 +5,9 @@ import logging import urllib -from plaso import filters from plaso.analysis import interface from plaso.analysis import manager +from plaso.filters import manager as filters_manager from plaso.formatters import manager as formatters_manager from plaso.lib import event @@ -194,7 +194,7 @@ def __init__(self, incoming_queue): self._search_term_timeline = [] for filter_str, call_back in self.FILTERS: - filter_obj = filters.GetFilter(filter_str) + filter_obj = filters_manager.FiltersManager.GetFilterObject(filter_str) call_back_obj = getattr(FilterClass, call_back, None) if filter_obj and call_back_obj: self._filter_dict[filter_obj] = (call_back, call_back_obj) diff --git a/plaso/analysis/tagging.py b/plaso/analysis/tagging.py index 6e6ade0923..00fb5b3598 100644 --- a/plaso/analysis/tagging.py +++ b/plaso/analysis/tagging.py @@ -6,7 +6,7 @@ from plaso.analysis import interface from plaso.analysis import manager -from plaso import filters +from plaso.filters import manager as filters_manager from plaso.lib import event @@ -120,20 +120,24 @@ def _ParseTaggingFile(self, input_path): line_strip = line_rstrip.lstrip() if not line_strip or line_strip.startswith(u'#'): continue + if not line_rstrip[0].isspace(): current_tag = line_rstrip tags[current_tag] = [] else: if not current_tag: continue - compiled_filter = filters.GetFilter(line_strip) - if compiled_filter: - if compiled_filter not in tags[current_tag]: - tags[current_tag].append(compiled_filter) - else: + + compiled_filter = filters_manager.FiltersManager.GetFilterObject( + line_strip) + if not compiled_filter: logging.warning( u'Tag "{0:s}" contains invalid filter: {1:s}'.format( current_tag, line_strip)) + + elif compiled_filter not in tags[current_tag]: + tags[current_tag].append(compiled_filter) + return tags def CompileReport(self, analysis_mediator): diff --git a/plaso/filters/__init__.py b/plaso/filters/__init__.py index 7923f751da..e33967aa72 100644 --- a/plaso/filters/__init__.py +++ b/plaso/filters/__init__.py @@ -1,40 +1,6 @@ # -*- coding: utf-8 -*- -import logging +"""This file contains an import statement for each filter.""" from plaso.filters import dynamic_filter -from plaso.filters import eventfilter -from plaso.filters import filterlist - -from plaso.lib import filter_interface -from plaso.lib import errors - - -def ListFilters(): - """Generate a list of all available filters.""" - filters = [] - for cl in filter_interface.FilterObject.classes: - filters.append(filter_interface.FilterObject.classes[cl]()) - - return filters - - -def GetFilter(filter_string): - """Returns the first filter that matches the filter string. - - Args: - filter_string: A filter string for any of the available filters. - - Returns: - The first FilterObject found matching the filter string. If no FilterObject - is available for this filter string None is returned. - """ - if not filter_string: - return - - for filter_obj in ListFilters(): - try: - filter_obj.CompileFilter(filter_string) - return filter_obj - except errors.WrongPlugin: - logging.debug(u'Filterstring [{}] is not a filter: {}'.format( - filter_string, filter_obj.filter_name)) +from plaso.filters import event_filter +from plaso.filters import filter_list diff --git a/plaso/filters/dynamic_filter.py b/plaso/filters/dynamic_filter.py index b751558f72..0c84caaaeb 100644 --- a/plaso/filters/dynamic_filter.py +++ b/plaso/filters/dynamic_filter.py @@ -1,11 +1,20 @@ # -*- coding: utf-8 -*- +"""The dynamic event object filter.""" + +from plaso.filters import event_filter +from plaso.filters import manager from plaso.lib import errors from plaso.lib import lexer -from plaso.filters import eventfilter +# TODO: move this to lib.lexer ? class SelectiveLexer(lexer.Lexer): - """A simple selective filter lexer implementation.""" + """Selective filter lexer implementation. + + The selective (or dynamic) filter allow to construct filter expressions + like: + SELECT field_a, field_b WHERE attribute contains 'text' + """ tokens = [ lexer.Token('INITIAL', r'SELECT', '', 'FIELDS'), @@ -31,31 +40,61 @@ class SelectiveLexer(lexer.Lexer): lexer.Token('LIMIT_END', r'(.+)$', 'SetLimit', 'END')] def __init__(self, data=''): - """Initialize the lexer.""" + """Initializes a selective lexer object. + + Args: + data: optional initial data to be processed by the lexer. + """ + super(SelectiveLexer, self).__init__(data=data) self.fields = [] self.limit = 0 self.lex_filter = None self.separator = u',' - super(SelectiveLexer, self).__init__(data) - def SetFilter(self, match, **_): - """Set the filter query.""" + def SetFields(self, match, **unused_kwargs): + """Sets the output fields. + + The output fields is the part of the filter expression directly following + the SELECT statement. + + Args: + match: the match object (instance of re.MatchObject) that contains the + output field names. + """ + text = match.group(1).lower() + field_text, _, _ = text.partition(' from ') + + use_field_text = field_text.replace(' ', '') + if ',' in use_field_text: + self.fields = use_field_text.split(',') + else: + self.fields = [use_field_text] + + def SetFilter(self, match, **unused_kwargs): + """Set the filter query. + + The filter query is the part of the filter expression directly following + the WHERE statement. + + Args: + match: the match object (instance of re.MatchObject) that contains the + filter query. + """ filter_match = match.group(1) if 'LIMIT' in filter_match: # This only occurs in the case where we have "LIMIT X SEPARATED BY". self.lex_filter, _, push_back = filter_match.rpartition('LIMIT') - self.PushBack('LIMIT {} SEPARATED BY '.format(push_back)) + self.PushBack('LIMIT {0:s} SEPARATED BY '.format(push_back)) else: self.lex_filter = filter_match - def SetSeparator(self, match, **_): - """Set the separator of the output, only uses the first char.""" - separator = match.group(1) - if separator: - self.separator = separator[0] + def SetLimit(self, match, **unused_kwargs): + """Sets the row limit. - def SetLimit(self, match, **_): - """Set the row limit.""" + Args: + match: the match object (instance of re.MatchObject) that contains the + row limit. + """ try: limit = int(match.group(1)) except ValueError: @@ -65,83 +104,96 @@ def SetLimit(self, match, **_): self.limit = limit - def SetFields(self, match, **_): - """Set the selective fields.""" - text = match.group(1).lower() - field_text, _, _ = text.partition(' from ') + def SetSeparator(self, match, **unused_kwargs): + """Sets the output field separator. - use_field_text = field_text.replace(' ', '') - if ',' in use_field_text: - self.fields = use_field_text.split(',') - else: - self.fields = [use_field_text] + Args: + match: the match object (instance of re.MatchObject) that contains the + output field separate. Note that only the first character is used. + """ + separator = match.group(1) + if separator: + self.separator = separator[0] -class DynamicFilter(eventfilter.EventObjectFilter): - """A twist to the EventObjectFilter allowing output fields to be selected. +class DynamicFilter(event_filter.EventObjectFilter): + """Event object filter that supports selective output fields. - This filter is essentially the same as the EventObjectFilter except it wraps + This filter is essentially the same as the event object filter except it wraps it in a selection of which fields should be included by an output module that - has support for selective fields. That is to say the filter: + supports selective fields, e.g. SELECT field_a, field_b WHERE attribute contains 'text' - Will use the EventObjectFilter "attribute contains 'text'" and at the same + Will use the event object filter "attribute contains 'text'" and at the same time indicate to the appropriate output module that the user wants only the fields field_a and field_b to be used in the output. """ + _STATE_END = u'END' + + def __init__(self): + """Initializes a filter object.""" + super(DynamicFilter, self).__init__() + self._fields = [] + self._limit = 0 + self._separator = u',' + @property def fields(self): - """Set the fields property.""" + """The output fields.""" return self._fields @property def limit(self): - """Return the limit of row counts.""" + """The row limit.""" return self._limit @property def separator(self): - """Return the separator value.""" + """The output field separator value.""" return self._separator - def __init__(self): - """Initialize the selective EventObjectFilter.""" - super(DynamicFilter, self).__init__() - self._fields = [] - self._limit = 0 - self._separator = u',' + def CompileFilter(self, filter_expression): + """Compiles the filter expression. + + The filter expression contains an object filter expression extended + with selective field selection. - def CompileFilter(self, filter_string): - """Compile the filter string into a EventObjectFilter matcher.""" - lex = SelectiveLexer(filter_string) + Args: + filter_expression: string that contains the filter expression. - _ = lex.NextToken() - if lex.error: - raise errors.WrongPlugin('Malformed filter string.') + Raises: + WrongPlugin: if the filter could not be compiled. + """ + lexer_object = SelectiveLexer(filter_expression) - _ = lex.NextToken() - if lex.error: - raise errors.WrongPlugin('No fields defined.') + lexer_object.NextToken() + if lexer_object.error: + raise errors.WrongPlugin(u'Malformed filter string.') - if lex.state is not 'END': - while lex.state is not 'END': - _ = lex.NextToken() - if lex.error: - raise errors.WrongPlugin('No filter defined for DynamicFilter.') + lexer_object.NextToken() + if lexer_object.error: + raise errors.WrongPlugin(u'No fields defined.') - if lex.state != 'END': + while lexer_object.state != self._STATE_END: + lexer_object.NextToken() + if lexer_object.error: + raise errors.WrongPlugin(u'No filter defined for DynamicFilter.') + + if lexer_object.state != self._STATE_END: raise errors.WrongPlugin( - 'Malformed DynamicFilter, end state not reached.') + u'Malformed DynamicFilter, end state not reached.') - self._fields = lex.fields - self._limit = lex.limit - self._separator = unicode(lex.separator) + self._fields = lexer_object.fields + self._limit = lexer_object.limit + self._separator = u'{0:s}'.format(lexer_object.separator) - if lex.lex_filter: - super(DynamicFilter, self).CompileFilter(lex.lex_filter) + if lexer_object.lex_filter: + super(DynamicFilter, self).CompileFilter(lexer_object.lex_filter) else: self._matcher = None - self._filter_expression = filter_string + self._filter_expression = filter_expression + +manager.FiltersManager.RegisterFilter(DynamicFilter) diff --git a/plaso/filters/event_filter.py b/plaso/filters/event_filter.py new file mode 100644 index 0000000000..b637c1fbfe --- /dev/null +++ b/plaso/filters/event_filter.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +"""The event object filter.""" + +from plaso.filters import interface +from plaso.filters import manager +from plaso.lib import errors + + +class EventObjectFilter(interface.FilterObject): + """Class that implements an event object filter.""" + + def CompileFilter(self, filter_expression): + """Compiles the filter expression. + + The filter expression contains an object filter expression. + + Args: + filter_expression: string that contains the filter expression. + + Raises: + WrongPlugin: if the filter could not be compiled. + """ + matcher = self._GetMatcher(filter_expression) + if not matcher: + raise errors.WrongPlugin(u'Malformed filter expression.') + + self._filter_expression = filter_expression + self._matcher = matcher + + def Match(self, event_object): + """Determines if an event object matches the filter. + + Args: + event_object: an event object (instance of EventObject). + + Returns: + A boolean value that indicates a match. + """ + if not self._matcher: + return True + + self._decision = self._matcher.Matches(event_object) + return self._decision + + +manager.FiltersManager.RegisterFilter(EventObjectFilter) diff --git a/plaso/filters/eventfilter.py b/plaso/filters/eventfilter.py deleted file mode 100644 index 37d95186c4..0000000000 --- a/plaso/filters/eventfilter.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -from plaso.lib import errors -from plaso.lib import filter_interface -from plaso.lib import pfilter - - -class EventObjectFilter(filter_interface.FilterObject): - """A simple filter using the objectfilter library.""" - - def CompileFilter(self, filter_string): - """Compile the filter string into a filter matcher.""" - self._matcher = pfilter.GetMatcher(filter_string, True) - if not self._matcher: - raise errors.WrongPlugin('Malformed filter string.') - self._filter_expression = filter_string - - def Match(self, event_object): - """Evaluate an EventObject against a filter.""" - if not self._matcher: - return True - - self._decision = self._matcher.Matches(event_object) - - return self._decision - diff --git a/plaso/filters/filter_list.py b/plaso/filters/filter_list.py new file mode 100644 index 0000000000..bcbe12bfa1 --- /dev/null +++ b/plaso/filters/filter_list.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- +"""List of object filters.""" + +import logging +import os + +import yaml + +from plaso.filters import interface +from plaso.filters import manager +from plaso.lib import errors + + +class ObjectFilterList(interface.FilterObject): + """A list of filters with addtional metadata.""" + + def _IncludeKeyword(self, loader, node): + """Callback for YAML add_constructor. + + The YAML constructor is a function that converts a YAML node to a native + Python object. A YAML constructor accepts an instance of Loader and a node + and returns a Python object. For more information see: + http://pyyaml.org/wiki/PyYAMLDocumentation + + Args: + loader: the YAML loader object (instance of yaml.Loader). + node: a YAML node (instance of yaml.TODO). + + Returns: + A Python object or None. + """ + filename = loader.construct_scalar(node) + if not os.path.isfile(filename): + return + + with open(filename, 'rb') as file_object: + try: + return yaml.safe_load(file_object) + + except yaml.ParserError as exception: + logging.error( + u'Unable to load rule file with error: {0:s}'.format(exception)) + return + + def _ParseEntry(self, entry): + """Parses a single filter entry. + + Args: + entry: YAML string that defines a single object filter entry. + + Raises: + WrongPlugin: if the entry cannot be parsed. + """ + # A single file with a list of filters to parse. + for name, meta in entry.items(): + if u'filter' not in meta: + raise errors.WrongPlugin( + u'Entry inside {0:s} does not contain a filter statement.'.format( + name)) + + meta_filter = meta.get(u'filter') + matcher = self._GetMatcher(meta_filter) + if not matcher: + raise errors.WrongPlugin( + u'Filter entry [{0:s}] malformed for rule: <{1:s}>'.format( + meta_filter, name)) + + self.filters.append((name, matcher, meta)) + + def CompileFilter(self, filter_expression): + """Compiles the filter expression. + + The filter expression contains the name of a YAML file. + + Args: + filter_expression: string that contains the filter expression. + + Raises: + WrongPlugin: if the filter could not be compiled. + """ + if not os.path.isfile(filter_expression): + raise errors.WrongPlugin(( + u'ObjectFilterList requires an YAML file to be passed on, ' + u'this filter string is not a file.')) + + yaml.add_constructor( + u'!include', self._IncludeKeyword, Loader=yaml.loader.SafeLoader) + results = None + + with open(filter_expression, 'rb') as file_object: + try: + results = yaml.safe_load(file_object) + except (yaml.scanner.ScannerError, IOError) as exception: + raise errors.WrongPlugin( + u'Unable to parse YAML file with error: {0:s}.'.format(exception)) + + self.filters = [] + results_type = type(results) + if results_type is dict: + self._ParseEntry(results) + elif results_type is list: + for result in results: + if not isinstance(result, dict): + raise errors.WrongPlugin( + u'Wrong format of YAML file, entry not a dict ({0:s})'.format( + results_type)) + self._ParseEntry(result) + else: + raise errors.WrongPlugin( + u'Wrong format of YAML file, entry not a dict ({0:s})'.format( + results_type)) + self._filter_expression = filter_expression + + def Match(self, event_object): + """Determines if an event object matches the filter. + + Args: + event_object: an event object (instance of EventObject). + + Returns: + A boolean value that indicates a match. + """ + if not self.filters: + return True + + for name, matcher, meta in self.filters: + self._decision = matcher.Matches(event_object) + if self._decision: + meta_description = meta.get(u'description', u'N/A') + meta_urls = meta.get(u'urls', []) + self._reason = u'[{0:s}] {1:s} {2:s}'.format( + name, meta_description, u' - '.join(meta_urls)) + return True + + return False + + +manager.FiltersManager.RegisterFilter(ObjectFilterList) diff --git a/plaso/filters/filterlist.py b/plaso/filters/filterlist.py deleted file mode 100644 index fc31ef3632..0000000000 --- a/plaso/filters/filterlist.py +++ /dev/null @@ -1,94 +0,0 @@ -# -*- coding: utf-8 -*- -import os -import yaml -import logging - -from plaso.lib import errors -from plaso.lib import filter_interface -from plaso.lib import pfilter - -# TODO: This file requires a cleanup to confirm with project style etc.. - -def IncludeKeyword(loader, node): - """A constructor for the include keyword in YAML.""" - filename = loader.construct_scalar(node) - if os.path.isfile(filename): - with open(filename, 'rb') as fh: - try: - data = yaml.safe_load(fh) - except yaml.ParserError as exception: - logging.error(u'Unable to load rule file with error: {0:s}'.format( - exception)) - return None - return data - - -class ObjectFilterList(filter_interface.FilterObject): - """A series of Pfilter filters along with metadata.""" - - def CompileFilter(self, filter_string): - """Compile a set of ObjectFilters defined in an YAML file.""" - if not os.path.isfile(filter_string): - raise errors.WrongPlugin(( - 'ObjectFilterList requires an YAML file to be passed on, this filter ' - 'string is not a file.')) - - yaml.add_constructor('!include', IncludeKeyword, - Loader=yaml.loader.SafeLoader) - results = None - - with open(filter_string, 'rb') as fh: - try: - results = yaml.safe_load(fh) - except (yaml.scanner.ScannerError, IOError) as exception: - raise errors.WrongPlugin( - u'Unable to parse YAML file with error: {0:s}.'.format(exception)) - - self.filters = [] - results_type = type(results) - if results_type is dict: - self._ParseEntry(results) - elif results_type is list: - for result in results: - if type(result) is not dict: - raise errors.WrongPlugin( - u'Wrong format of YAML file, entry not a dict ({})'.format( - results_type)) - self._ParseEntry(result) - else: - raise errors.WrongPlugin( - u'Wrong format of YAML file, entry not a dict ({})'.format( - results_type)) - self._filter_expression = filter_string - - def _ParseEntry(self, entry): - """Parse a single YAML filter entry.""" - # A single file with a list of filters to parse. - for name, meta in entry.items(): - if 'filter' not in meta: - raise errors.WrongPlugin( - u'Entry inside {} does not contain a filter statement.'.format( - name)) - - matcher = pfilter.GetMatcher(meta.get('filter'), True) - if not matcher: - raise errors.WrongPlugin( - u'Filter entry [{0:s}] malformed for rule: <{1:s}>'.format( - meta.get('filter'), name)) - - self.filters.append((name, matcher, meta)) - - def Match(self, event_object): - """Evaluate an EventObject against a pfilter.""" - if not self.filters: - return True - - for name, matcher, meta in self.filters: - self._decision = matcher.Matches(event_object) - if self._decision: - self._reason = u'[{}] {} {}'.format( - name, meta.get('description', 'N/A'), u' - '.join( - meta.get('urls', []))) - return True - - return diff --git a/plaso/filters/interface.py b/plaso/filters/interface.py new file mode 100644 index 0000000000..820802763c --- /dev/null +++ b/plaso/filters/interface.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +"""The filter interface classes.""" + +import abc + +from plaso.lib import errors +from plaso.lib import pfilter + + +class FilterObject(object): + """The filter interface class.""" + + def __init__(self): + """Initialize the filter object.""" + super(FilterObject, self).__init__() + self._filter_expression = None + self._matcher = None + + @property + def fields(self): + """Return a list of fields for adaptive output modules.""" + return [] + + @property + def filter_expression(self): + """Return the compiled filter expression or None if not compiled.""" + if self._filter_expression: + return self._filter_expression + + @property + def filter_name(self): + """Return the name of the filter.""" + return self.__class__.__name__ + + @property + def last_decision(self): + """The last matching decision or None.""" + return getattr(self, u'_decision', None) + + @property + def last_reason(self): + """The last reason for the match or None.""" + if getattr(self, u'_decision', False): + return getattr(self, u'_reason', u'') + + @property + def limit(self): + """The row limit.""" + return 0 + + @property + def separator(self): + """The output field separator value.""" + return u',' + + def _GetMatcher(self, filter_expression): + """Retrieves a filter object for a specific filter expression. + + Args: + filter_expression: string that contains the filter expression. + + Returns: + A filter object (instance of objectfilter.TODO) or None. + """ + try: + parser = pfilter.BaseParser(filter_expression).Parse() + return parser.Compile(pfilter.PlasoAttributeFilterImplementation) + + except errors.ParseError: + pass + + @abc.abstractmethod + def CompileFilter(self, filter_expression): + """Compiles the filter expression. + + Args: + filter_expression: string that contains the filter expression. + + Raises: + WrongPlugin: if the filter could not be compiled. + """ + + def Match(self, unused_event_object): + """Determines if an event object matches the filter. + + Args: + event_object: an event object (instance of EventObject). + + Returns: + A boolean value that indicates a match. + """ + return False diff --git a/plaso/filters/manager.py b/plaso/filters/manager.py new file mode 100644 index 0000000000..1584f6cf91 --- /dev/null +++ b/plaso/filters/manager.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +"""This file contains the event filters manager class.""" + +import logging + +from plaso.lib import errors + + +class FiltersManager(object): + """Class that implements the filters manager.""" + + _filter_classes = {} + + @classmethod + def DeregisterFilter(cls, filter_class): + """Deregisters a filter class. + + The filter classes are identified based on their lower case filter name. + + Args: + filter_class: the class object of the filter. + + Raises: + KeyError: if filter class is not set for the corresponding filter name. + """ + filter_name = filter_class.__name__ + if filter_name not in cls._filter_classes: + raise KeyError( + u'Filter class not set for filter name: {0:s}.'.format( + filter_class.__name__)) + + del cls._filter_classes[filter_name] + + @classmethod + def GetFilterObject(cls, filter_string): + """Creates instances of specific filters. + + Args: + filter_string: A filter string for any of the available filters. + + Returns: + The first filter found matching the filter string or None if no + corresponding filter is available. + """ + if not filter_string: + return + + # TODO: refactor not to instantiate all filter classes. + for filter_object in cls.GetFilterObjects(): + try: + filter_object.CompileFilter(filter_string) + return filter_object + + except errors.WrongPlugin: + logging.debug(u'Filter string [{0:s}] is not a filter: {1:s}'.format( + filter_string, filter_object.filter_name)) + + @classmethod + def GetFilterObjects(cls): + """Creates instances of the available filters. + + Returns: + A list of filter objects. + """ + return [filter_class() for filter_class in cls._filter_classes.values()] + + @classmethod + def RegisterFilter(cls, filter_class): + """Registers a filter class. + + The filter classes are identified based on their lower case filter name. + + Args: + filter_class: the class object of the filter. + + Raises: + KeyError: if filter class is already set for the corresponding + filter name. + """ + filter_name = filter_class.__name__ + if filter_name in cls._filter_classes: + raise KeyError(( + u'Filter class already set for filter name: {0:s}.').format( + filter_class.__name__)) + + cls._filter_classes[filter_name] = filter_class + + @classmethod + def RegisterFilters(cls, filter_classes): + """Registers filter classes. + + The filter classes are identified based on their lower case filter name. + + Args: + filter_classes: a list of class objects of the filters. + + Raises: + KeyError: if filter class is already set for the corresponding + filter name. + """ + for filter_class in filter_classes: + cls.RegisterFilter(filter_class) diff --git a/plaso/frontend/log2timeline.py b/plaso/frontend/log2timeline.py index 52f9a2e11f..d0e56db874 100644 --- a/plaso/frontend/log2timeline.py +++ b/plaso/frontend/log2timeline.py @@ -6,13 +6,14 @@ import plaso # The following import makes sure the filters are registered. -from plaso import filters +from plaso import filters # pylint: disable=unused-import # The following import makes sure the hashers are registered. from plaso import hashers # pylint: disable=unused-import # The following import makes sure the parsers are registered. from plaso import parsers # pylint: disable=unused-import # The following import makes sure the output modules are registered. from plaso import output # pylint: disable=unused-import +from plaso.filters import manager as filters_manager from plaso.frontend import extraction_frontend from plaso.output import manager as output_manager @@ -44,7 +45,8 @@ def _GetFiltersInformation(self): A list of tuples of filter names and docstrings. """ filters_information = [] - for filter_object in sorted(filters.ListFilters()): + for filter_object in sorted( + filters_manager.FiltersManager.GetFilterObjects()): # TODO: refactor to use DESCRIPTION instead of docstring. doc_string, _, _ = filter_object.__doc__.partition(u'\n') filters_information.append((filter_object.filter_name, doc_string)) diff --git a/plaso/lib/errors.py b/plaso/lib/errors.py index aab76e43d9..29f11b3f4b 100644 --- a/plaso/lib/errors.py +++ b/plaso/lib/errors.py @@ -24,6 +24,7 @@ class ConnectionError(Error): class EngineAbort(Error): """Class that defines an engine initiated abort exception.""" + class FileSystemScannerError(Error): """Raised when a there is an issue scanning for a file system.""" @@ -36,6 +37,10 @@ class NoFormatterFound(Error): """Raised when no formatter is found for a particular event.""" +class ParseError(Error): + """Raised when a parse error occurred.""" + + class PathNotFound(Error): """Raised when a preprocessor fails to fill in a path variable.""" diff --git a/plaso/lib/filter_interface.py b/plaso/lib/filter_interface.py deleted file mode 100644 index e940c2e8e0..0000000000 --- a/plaso/lib/filter_interface.py +++ /dev/null @@ -1,92 +0,0 @@ -# -*- coding: utf-8 -*- -"""A definition of the filter interface for filters in plaso.""" - -import abc - -from plaso.lib import errors -from plaso.lib import registry - - -class FilterObject(object): - """The interface that each filter needs to implement in plaso.""" - - # TODO: Re-factor into filters/interface and use a manager instead - # of the registry library. - __metaclass__ = registry.MetaclassRegistry - __abstract = True - - @property - def filter_expression(self): - """Return the compiled filter expression or None if not compiled.""" - if self._filter_expression: - return self._filter_expression - - @property - def filter_name(self): - """Return the name of the filter.""" - return self.__class__.__name__ - - @property - def last_decision(self): - """Return the last matching decision.""" - return getattr(self, '_decision', None) - - @property - def last_reason(self): - """Return the last reason for the match, if there was one.""" - if getattr(self, 'last_decision', False): - return getattr(self, '_reason', '') - - @property - def fields(self): - """Return a list of fields for adaptive output modules.""" - return [] - - @property - def separator(self): - """Return a separator for adaptive output modules.""" - return ',' - - @property - def limit(self): - """Returns the max number of records to return, or zero for all records.""" - return 0 - - def __init__(self): - """Initialize the filter object.""" - super(FilterObject, self).__init__() - self._filter_expression = None - self._matcher = None - - @abc.abstractmethod - def CompileFilter(self, unused_filter_string): - """Verify filter string and prepare the filter for later usage. - - This function verifies the filter string matches the definition of - the class and if necessary compiles or prepares the filter so it can start - matching against passed in EventObjects. - - Args: - unused_filter_string: A string passed in that should be recognized by - the filter class. - - Raises: - errors.WrongPlugin: If this filter string does not match the filter - class. - """ - raise errors.WrongPlugin('Not the correct filter for this string.') - - def Match(self, unused_event_object): - """Compare an EventObject to the filter expression and return a boolean. - - This function returns True if the filter should be passed through the filter - and False otherwise. - - Args: - unused_event_object: An event object (instance of EventObject) that - should be evaluated against the filter. - - Returns: - Boolean indicating whether the filter matches the object or not. - """ - return False diff --git a/plaso/lib/lexer.py b/plaso/lib/lexer.py index 834b51a200..2cca0b7966 100644 --- a/plaso/lib/lexer.py +++ b/plaso/lib/lexer.py @@ -8,6 +8,8 @@ import logging import re +from plaso.lib import errors + class Token(object): """A token action.""" @@ -34,16 +36,9 @@ def __init__(self, state_regex, regex, actions, next_state, flags=re.I): self.next_state = next_state - def Action(self, lexer): + def Action(self, unused_lexer): """Method is called when the token matches.""" - - -class Error(Exception): - """Module exception.""" - - -class ParseError(Error): - """A parse error occurred.""" + pass class Lexer(object): @@ -57,7 +52,11 @@ class Lexer(object): tokens = [] def __init__(self, data=''): - """Initializes the lexer object.""" + """Initializes the lexer object. + + Args: + data: optional initial data to be processed by the lexer. + """ super(Lexer, self).__init__() self.buffer = data self.error = 0 @@ -102,7 +101,7 @@ def NextToken(self): # Override the state from the Token elif possible_next_state: next_state = possible_next_state - except ParseError as exception: + except errors.ParseError as exception: self.Error(exception) # Update the next state @@ -118,30 +117,37 @@ def NextToken(self): self.buffer = self.buffer[1:] return self._ERROR_TOKEN - def Feed(self, data): - """Feed the buffer with data.""" - self.buffer = ''.join([self.buffer, data]) - - def Empty(self): - """Return a boolean indicating if the buffer is empty.""" - return not self.buffer + def Close(self): + """A convenience function to force us to parse all the data.""" + while self.NextToken(): + if not self.buffer: + return def Default(self, **kwarg): """The default callback handler.""" logging.debug(u'Default handler: {0:s}'.format(kwarg)) + def Empty(self): + """Returns a boolean indicating if the buffer is empty.""" + return not self.buffer + def Error(self, message=None, weight=1): - """Log an error down.""" + """Log an error down. + + Args: + message: optional error message. + weight: optional error weight. + """ logging.debug(u'Error({0:d}): {1:s}'.format(weight, message)) # Keep a count of errors self.error += weight - def PushState(self, **_): + def PushState(self, **unused_kwargs): """Push the current state on the state stack.""" logging.debug(u'Storing state {0:s}'.format(repr(self.state))) self.state_stack.append(self.state) - def PopState(self, **_): + def PopState(self, **unused_kwargs): """Pop the previous state from the stack.""" try: self.state = self.state_stack.pop() @@ -152,17 +158,23 @@ def PopState(self, **_): self.Error( u'Tried to pop the state but failed - possible recursion error') - def PushBack(self, string='', **_): - """Push the match back on the stream.""" + def Feed(self, data): + """Feed the buffer with data. + + Args: + data: data to be processed by the lexer. + """ + self.buffer = ''.join([self.buffer, data]) + + def PushBack(self, string='', **unused_kwargs): + """Push the match back on the stream. + + Args: + string: optional data. + """ self.buffer = string + self.buffer self.processed_buffer = self.processed_buffer[:-len(string)] - def Close(self): - """A convenience function to force us to parse all the data.""" - while self.NextToken(): - if not self.buffer: - return - class SelfFeederMixIn(Lexer): """This mixin is used to make a lexer which feeds itself. @@ -180,22 +192,30 @@ def __init__(self, file_object=None): super(SelfFeederMixIn, self).__init__() self.file_object = file_object + def Feed(self, size=512): + """Feed data into the buffer. + + Args: + size: optional data size to read form the file-like object. + """ + data = self.file_object.read(size) + Lexer.Feed(self, data) + return len(data) + def NextToken(self): - """Return the next token.""" + """Retrieves the next token. + + Returns: + The next token (instance of Token) or None. + """ # If we don't have enough data - feed ourselves: We assume # that we must have at least one sector in our buffer. if len(self.buffer) < 512: if self.Feed() == 0 and not self.buffer: - return None + return return Lexer.NextToken(self) - def Feed(self, size=512): - """Feed data into the buffer.""" - data = self.file_object.read(size) - Lexer.Feed(self, data) - return len(data) - class Expression(object): """A class representing an expression.""" @@ -210,13 +230,10 @@ def __init__(self): """Initializes the expression object.""" self.args = [] - def SetAttribute(self, attribute): - """Set the attribute.""" - self.attribute = attribute - - def SetOperator(self, operator): - """Set the operator.""" - self.operator = operator + def __str__(self): + """Return a string representation of the expression.""" + return 'Expression: ({0:s}) ({1:s}) {2:s}'.format( + self.attribute, self.operator, self.args) def AddArg(self, arg): """Adds a new arg to this expression. @@ -232,27 +249,30 @@ def AddArg(self, arg): """ self.args.append(arg) if len(self.args) > self.number_of_args: - raise ParseError(u'Too many args for this expression.') + raise errors.ParseError(u'Too many args for this expression.') elif len(self.args) == self.number_of_args: return True return False - def __str__(self): - """Return a string representation of the expression.""" - return 'Expression: ({0:s}) ({1:s}) {2:s}'.format( - self.attribute, self.operator, self.args) + def Compile(self, unused_filter_implementation): + """Given a filter implementation, compile this expression.""" + raise NotImplementedError( + u'{0:s} does not implement Compile.'.format(self.__class__.__name__)) # TODO: rename this function to GetTreeAsString or equivalent. def PrintTree(self, depth=''): """Print the tree.""" return u'{0:s} {1:s}'.format(depth, self) - def Compile(self, unused_filter_implementation): - """Given a filter implementation, compile this expression.""" - raise NotImplementedError( - u'{0:s} does not implement Compile.'.format(self.__class__.__name__)) + def SetAttribute(self, attribute): + """Set the attribute.""" + self.attribute = attribute + + def SetOperator(self, operator): + """Set the operator.""" + self.operator = operator class BinaryExpression(Expression): @@ -276,8 +296,9 @@ def AddOperands(self, lhs, rhs): if isinstance(lhs, Expression) and isinstance(rhs, Expression): self.args = [lhs, rhs] else: - raise ParseError(u'Expected expression, got {0:s} {1:s} {2:s}'.format( - lhs, self.operator, rhs)) + raise errors.ParseError( + u'Expected expression, got {0:s} {1:s} {2:s}'.format( + lhs, self.operator, rhs)) # TODO: rename this function to GetTreeAsString or equivalent. def PrintTree(self, depth=''): @@ -296,7 +317,8 @@ def Compile(self, filter_implementation): elif operator == 'or' or operator == '||': method = 'OrFilter' else: - raise ParseError(u'Invalid binary operator {0:s}'.format(operator)) + raise errors.ParseError( + u'Invalid binary operator {0:s}'.format(operator)) args = [x.Compile(filter_implementation) for x in self.args] return getattr(filter_implementation, method)(*args) @@ -364,42 +386,43 @@ def __init__(self, data): self.stack = [] Lexer.__init__(self, data) - def BinaryOperator(self, string=None, **_): + def BinaryOperator(self, string=None, **unused_kwargs): """Set the binary operator.""" self.stack.append(self.binary_expression_cls(string)) - def BracketOpen(self, **_): + def BracketOpen(self, **unused_kwargs): """Define an open bracket.""" self.stack.append('(') - def BracketClose(self, **_): + def BracketClose(self, **unused_kwargs): """Close the bracket.""" self.stack.append(')') - def StringStart(self, **_): + def StringStart(self, **unused_kwargs): """Initialize the string.""" self.string = '' - def StringEscape(self, string, match, **_): + def StringEscape(self, string, match, **unused_kwargs): """Escape backslashes found inside a string quote. Backslashes followed by anything other than ['"rnbt] will just be included in the string. Args: - string: The string that matched. - match: The match object (m.group(1) is the escaped code) + string: The string that matched. + match: the match object (instance of re.MatchObject). + Where match.group(1) contains the escaped code. """ if match.group(1) in '\'"rnbt': self.string += string.decode('string_escape') else: self.string += string - def StringInsert(self, string='', **_): + def StringInsert(self, string='', **unused_kwargs): """Add to the string.""" self.string += string - def StringFinish(self, **_): + def StringFinish(self, **unused_kwargs): """Finish the string operation.""" if self.state == 'ATTRIBUTE': return self.StoreAttribute(string=self.string) @@ -407,7 +430,7 @@ def StringFinish(self, **_): elif self.state == 'ARG_LIST': return self.InsertArg(string=self.string) - def StoreAttribute(self, string='', **_): + def StoreAttribute(self, string='', **unused_kwargs): """Store the attribute.""" logging.debug(u'Storing attribute {0:s}'.format(repr(string))) @@ -415,16 +438,16 @@ def StoreAttribute(self, string='', **_): try: self.current_expression.SetAttribute(string) except AttributeError: - raise ParseError(u'Invalid attribute \'{0:s}\''.format(string)) + raise errors.ParseError(u'Invalid attribute \'{0:s}\''.format(string)) return 'OPERATOR' - def StoreOperator(self, string='', **_): + def StoreOperator(self, string='', **unused_kwargs): """Store the operator.""" logging.debug(u'Storing operator {0:s}'.format(repr(string))) self.current_expression.SetOperator(string) - def InsertArg(self, string='', **_): + def InsertArg(self, string='', **unused_kwargs): """Insert an arg to the current expression.""" logging.debug(u'Storing Argument {0:s}'.format(string)) @@ -485,9 +508,10 @@ def Reduce(self): def Error(self, message=None, unused_weight=1): """Raise an error message.""" - raise ParseError(u'{0:s} in position {1:s}: {2:s} <----> {3:s} )'.format( - message, len(self.processed_buffer), self.processed_buffer, - self.buffer)) + raise errors.ParseError( + u'{0:s} in position {1:s}: {2:s} <----> {3:s} )'.format( + message, len(self.processed_buffer), self.processed_buffer, + self.buffer)) def Parse(self): """Parse.""" diff --git a/plaso/lib/objectfilter.py b/plaso/lib/objectfilter.py index 9ce07f7c63..cbeb8119ad 100644 --- a/plaso/lib/objectfilter.py +++ b/plaso/lib/objectfilter.py @@ -100,23 +100,12 @@ def __init__(self, brand, code): import logging import re +from plaso.lib import errors from plaso.lib import lexer from plaso.lib import utils -class Error(Exception): - """Base module exception.""" - - -class MalformedQueryError(Error): - """The provided filter query is malformed.""" - - -class ParseError(Error): - """The parser for textual queries returned invalid results.""" - - -class InvalidNumberOfOperands(Error): +class InvalidNumberOfOperands(errors.Error): """The number of operands provided to this operator is wrong.""" @@ -133,13 +122,13 @@ def __init__(self, arguments=None, value_expander=None): subclassing ValueExpander. Raises: - Error: If the given value_expander is not a subclass of ValueExpander + ValueError: If the given value_expander is not a subclass of ValueExpander """ self.value_expander = None self.value_expander_cls = value_expander if self.value_expander_cls: if not issubclass(self.value_expander_cls, ValueExpander): - raise Error(u'{0:s} is not a valid value expander'.format( + raise ValueError(u'{0:s} is not a valid value expander'.format( self.value_expander_cls)) self.value_expander = self.value_expander_cls() self.args = arguments or [] @@ -584,7 +573,7 @@ def Compile(self, filter_implementation): operator = filter_implementation.OPS.get(op_str, None) if not operator: - raise ParseError(u'Unknown operator {0:s} provided.'.format( + raise errors.ParseError(u'Unknown operator {0:s} provided.'.format( self.operator)) arguments.extend(self.args) @@ -616,7 +605,8 @@ def SetExpression(self, expression): if isinstance(expression, lexer.Expression): self.args = [expression] else: - raise ParseError(u'Expected expression, got {0:s}.'.format(expression)) + raise errors.ParseError( + u'Expected expression, got {0:s}.'.format(expression)) def Compile(self, filter_implementation): """Compile the expression.""" @@ -638,7 +628,8 @@ def Compile(self, filter_implementation): elif operator == 'or' or operator == '||': method = 'OrFilter' else: - raise ParseError(u'Invalid binary operator {0:s}.'.format(operator)) + raise errors.ParseError( + u'Invalid binary operator {0:s}.'.format(operator)) args = [x.Compile(filter_implementation) for x in self.args] return filter_implementation.FILTERS[method](arguments=args) @@ -712,7 +703,7 @@ def StoreAttribute(self, string='', **kwargs): def FlipAllowed(self): """Raise an error if the not keyword is used where it is not allowed.""" if not hasattr(self, 'flipped'): - raise ParseError(u'Not defined.') + raise errors.ParseError(u'Not defined.') if not self.flipped: return @@ -720,7 +711,7 @@ def FlipAllowed(self): if self.current_expression.operator: if not self.current_expression.operator.lower() in ( 'is', 'contains', 'inset', 'equals'): - raise ParseError( + raise errors.ParseError( u'Keyword \'not\' does not work against operator: {0:s}'.format( self.current_expression.operator)) @@ -731,10 +722,11 @@ def FlipLogic(self, **unused_kwargs): is met this logic will flip that to False, and vice versa. """ if hasattr(self, 'flipped') and self.flipped: - raise ParseError(u'The operator \'not\' can only be expressed once.') + raise errors.ParseError( + u'The operator \'not\' can only be expressed once.') if self.current_expression.args: - raise ParseError( + raise errors.ParseError( u'Unable to place the keyword \'not\' after an argument.') self.flipped = True @@ -769,7 +761,7 @@ def InsertFloatArg(self, string='', **unused_kwargs): try: float_value = float(string) except (TypeError, ValueError): - raise ParseError(u'{0:s} is not a valid float.'.format(string)) + raise errors.ParseError(u'{0:s} is not a valid float.'.format(string)) return self.InsertArg(float_value) def InsertIntArg(self, string='', **unused_kwargs): @@ -777,7 +769,7 @@ def InsertIntArg(self, string='', **unused_kwargs): try: int_value = int(string) except (TypeError, ValueError): - raise ParseError(u'{0:s} is not a valid integer.'.format(string)) + raise errors.ParseError(u'{0:s} is not a valid integer.'.format(string)) return self.InsertArg(int_value) def InsertInt16Arg(self, string='', **unused_kwargs): @@ -785,7 +777,8 @@ def InsertInt16Arg(self, string='', **unused_kwargs): try: int_value = int(string, 16) except (TypeError, ValueError): - raise ParseError(u'{0:s} is not a valid base16 integer.'.format(string)) + raise errors.ParseError( + u'{0:s} is not a valid base16 integer.'.format(string)) return self.InsertArg(int_value) def StringFinish(self, **unused_kwargs): @@ -803,7 +796,8 @@ def StringEscape(self, string, match, **unused_kwargs): Args: string: The string that matched. - match: The match object (m.group(1) is the escaped code) + match: the match object (instance of re.MatchObject). + Where match.group(1) contains the escaped code. Raises: ParseError: When the escaped string is not one of [\'"rnbt] @@ -811,7 +805,7 @@ def StringEscape(self, string, match, **unused_kwargs): if match.group(1) in '\\\'"rnbt\\.ws': self.string += string.decode('string_escape') else: - raise ParseError(u'Invalid escape character {0:s}.'.format(string)) + raise errors.ParseError(u'Invalid escape character {0:s}.'.format(string)) def HexEscape(self, string, match, **unused_kwargs): """Converts a hex escaped string.""" @@ -820,7 +814,7 @@ def HexEscape(self, string, match, **unused_kwargs): try: self.string += binascii.unhexlify(hex_string) except TypeError: - raise ParseError(u'Invalid hex escape {0:s}.'.format(string)) + raise errors.ParseError(u'Invalid hex escape {0:s}.'.format(string)) def ContextOperator(self, string='', **unused_kwargs): self.stack.append(self.context_cls(string[1:])) @@ -845,15 +839,16 @@ def Reduce(self): length = len(self.stack) if length != 1: - self.Error(u'Illegal query expression.') + self.Error(u'Illegal query expression') return self.stack[0] def Error(self, message=None, _=None): # Note that none of the values necessarily are strings. - raise ParseError(u'{0!s} in position {1!s}: {2!s} <----> {3!s} )'.format( - message, len(self.processed_buffer), self.processed_buffer, - self.buffer)) + raise errors.ParseError( + u'{0!s} in position {1!s}: {2!s} <----> {3!s} )'.format( + message, len(self.processed_buffer), self.processed_buffer, + self.buffer)) def _CombineBinaryExpressions(self, operator): for i in range(1, len(self.stack)-1): diff --git a/plaso/lib/pfilter.py b/plaso/lib/pfilter.py index 8e0522b041..dc1cfa3989 100644 --- a/plaso/lib/pfilter.py +++ b/plaso/lib/pfilter.py @@ -159,13 +159,24 @@ class PlasoExpression(objectfilter.BasicExpression): } def Compile(self, filter_implementation): + """Compiles the filter implementation. + + Args: + filter_implementation: a filter object (instance of objectfilter.TODO). + + Returns: + A filter operator (instance of TODO). + + Raises: + ParserError: if an unknown operator is provided. + """ self.attribute = self.swap_source.get(self.attribute, self.attribute) arguments = [self.attribute] op_str = self.operator.lower() operator = filter_implementation.OPS.get(op_str, None) if not operator: - raise objectfilter.ParseError(u'Unknown operator {0:s} provided.'.format( + raise errors.ParseError(u'Unknown operator {0:s} provided.'.format( self.operator)) # Plaso specific implementation - if we are comparing a timestamp @@ -440,17 +451,3 @@ def GetTimeRange(cls): return first, last else: return last, first - - -def GetMatcher(query, quiet=False): - """Return a filter match object for a given query.""" - matcher = None - try: - parser = BaseParser(query).Parse() - matcher = parser.Compile(PlasoAttributeFilterImplementation) - except objectfilter.ParseError as exception: - if not quiet: - logging.error(u'Filter <{0:s}> malformed: {1:s}'.format( - query, exception)) - - return matcher diff --git a/plaso/lib/registry.py b/plaso/lib/registry.py deleted file mode 100644 index d552698c48..0000000000 --- a/plaso/lib/registry.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- -"""This file contains a class registration system for plugins.""" - -import abc - - -class MetaclassRegistry(abc.ABCMeta): - """Automatic Plugin Registration through metaclasses.""" - - def __init__(cls, name, bases, env_dict): - """Initialize a metaclass. - - Args: - name: The interface class name. - bases: A tuple of base names. - env_dict: The namespace of the object. - - Raises: - KeyError: If a classes given name is already registered, to make sure - no two classes that inherit from the same interface can have - the same name attribute. - """ - abc.ABCMeta.__init__(cls, name, bases, env_dict) - - # Register the name of the immediate parent class. - if bases: - cls.parent_class_name = getattr(bases[0], 'NAME', bases[0]) - cls.parent_class = bases[0] - - # Attach the classes dict to the baseclass and have all derived classes - # use the same one: - for base in bases: - try: - cls.classes = base.classes - cls.plugin_feature = base.plugin_feature - cls.top_level_class = base.top_level_class - break - except AttributeError: - cls.classes = {} - cls.plugin_feature = cls.__name__ - # Keep a reference to the top level class - cls.top_level_class = cls - - # The following should not be registered as they are abstract. Classes - # are abstract if the have the __abstract attribute (note this is not - # inheritable so each abstract class must be explicitly marked). - abstract_attribute = '_{0:s}__abstract'.format(name) - if getattr(cls, abstract_attribute, None): - return - - if not cls.__name__.startswith('Abstract'): - cls_name = getattr(cls, 'NAME', cls.__name__) - - if cls_name in cls.classes: - raise KeyError(u'Class: {0:s} already registered. [{1:s}]'.format( - cls_name, repr(cls))) - - cls.classes[cls_name] = cls - - try: - if cls.top_level_class.include_plugins_as_attributes: - setattr(cls.top_level_class, cls.__name__, cls) - except AttributeError: - pass diff --git a/plaso/parsers/selinux.py b/plaso/parsers/selinux.py index ab6f933494..c2f92a2f9f 100644 --- a/plaso/parsers/selinux.py +++ b/plaso/parsers/selinux.py @@ -107,7 +107,7 @@ def ParseTime(self, match=None, **unused_kwargs): logging.error( u'Unable to retrieve timestamp with error: {0:s}'.format(exception)) self.timestamp = 0 - raise lexer.ParseError(u'Not a valid timestamp.') + raise errors.ParseError(u'Not a valid timestamp.') def ParseString(self, match=None, **unused_kwargs): """Add a string to the body attribute. @@ -132,7 +132,7 @@ def ParseString(self, match=None, **unused_kwargs): def ParseFailed(self, **unused_kwargs): """Entry parsing failed callback.""" - raise lexer.ParseError(u'Unable to parse SELinux log line.') + raise errors.ParseError(u'Unable to parse SELinux log line.') def ParseLine(self, parser_mediator): """Parse a single line from the SELinux audit file. diff --git a/tests/filters/dynamic_filter.py b/tests/filters/dynamic_filter.py index 08ff3522d9..40b8041c85 100644 --- a/tests/filters/dynamic_filter.py +++ b/tests/filters/dynamic_filter.py @@ -1,70 +1,112 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +"""Tests for the dynamic event object filter.""" + import unittest from plaso.filters import dynamic_filter +from plaso.lib import errors from tests.filters import test_lib -class DynamicFilterTest(test_lib.FilterTestHelper): +class DynamicFilterTest(test_lib.FilterTestCase): """Tests for the DynamicFilter filter.""" - def setUp(self): - """Sets up the needed objects used throughout the test.""" - self.test_filter = dynamic_filter.DynamicFilter() - - def testFilterFail(self): - """Run few tests that should not be a proper filter.""" - self.TestFail('/tmp/file_that_most_likely_does_not_exist') - self.TestFail('some random stuff that is destined to fail') - self.TestFail('some_stuff is "random" and other_stuff ') - self.TestFail('some_stuff is "random" and other_stuff is not "random"') - self.TestFail('SELECT stuff FROM machine WHERE conditions are met') - self.TestFail('SELECT field_a, field_b WHERE ') - self.TestFail('SELECT field_a, field_b SEPARATED BY') - self.TestFail('SELECT field_a, SEPARATED BY field_b WHERE ') - self.TestFail('SELECT field_a, field_b LIMIT WHERE') - - def testFilterApprove(self): - self.TestTrue('SELECT stuff FROM machine WHERE some_stuff is "random"') - self.TestTrue('SELECT field_a, field_b, field_c') - self.TestTrue('SELECT field_a, field_b, field_c SEPARATED BY "%"') - self.TestTrue('SELECT field_a, field_b, field_c LIMIT 10') - self.TestTrue('SELECT field_a, field_b, field_c LIMIT 10 SEPARATED BY "|"') - self.TestTrue('SELECT field_a, field_b, field_c SEPARATED BY "|" LIMIT 10') - self.TestTrue('SELECT field_a, field_b, field_c WHERE date > "2012"') - self.TestTrue( - 'SELECT field_a, field_b, field_c WHERE date > "2012" LIMIT 100') - self.TestTrue(( - 'SELECT field_a, field_b, field_c WHERE date > "2012" SEPARATED BY "@"' - ' LIMIT 100')) - self.TestTrue(( - 'SELECT parser, date, time WHERE some_stuff is "random" and ' - 'date < "2021-02-14 14:51:23"')) + def testCompilerFilter(self): + """Tests the CompileFilter function.""" + test_filter = dynamic_filter.DynamicFilter() + + test_filter.CompileFilter( + u'SELECT stuff FROM machine WHERE some_stuff is "random"') + + test_filter.CompileFilter( + u'SELECT field_a, field_b, field_c') + + test_filter.CompileFilter( + u'SELECT field_a, field_b, field_c SEPARATED BY "%"') + + test_filter.CompileFilter( + u'SELECT field_a, field_b, field_c LIMIT 10') + + test_filter.CompileFilter( + u'SELECT field_a, field_b, field_c LIMIT 10 SEPARATED BY "|"') + + test_filter.CompileFilter( + u'SELECT field_a, field_b, field_c SEPARATED BY "|" LIMIT 10') + + test_filter.CompileFilter( + u'SELECT field_a, field_b, field_c WHERE date > "2012"') + + test_filter.CompileFilter( + u'SELECT field_a, field_b, field_c WHERE date > "2012" LIMIT 100') + + test_filter.CompileFilter(( + u'SELECT field_a, field_b, field_c WHERE date > "2012" SEPARATED BY ' + u'"@" LIMIT 100')) + + test_filter.CompileFilter(( + u'SELECT parser, date, time WHERE some_stuff is "random" and ' + u'date < "2021-02-14 14:51:23"')) + + with self.assertRaises(errors.WrongPlugin): + test_filter.CompileFilter( + u'/tmp/file_that_most_likely_does_not_exist') + + with self.assertRaises(errors.WrongPlugin): + test_filter.CompileFilter( + u'some random stuff that is destined to fail') + + with self.assertRaises(errors.WrongPlugin): + test_filter.CompileFilter( + u'some_stuff is "random" and other_stuff ') + + with self.assertRaises(errors.WrongPlugin): + test_filter.CompileFilter( + u'some_stuff is "random" and other_stuff is not "random"') + + with self.assertRaises(errors.WrongPlugin): + test_filter.CompileFilter( + u'SELECT stuff FROM machine WHERE conditions are met') + + with self.assertRaises(errors.WrongPlugin): + test_filter.CompileFilter(u'SELECT field_a, field_b WHERE ') + + with self.assertRaises(errors.WrongPlugin): + test_filter.CompileFilter(u'SELECT field_a, field_b SEPARATED BY') + + with self.assertRaises(errors.WrongPlugin): + test_filter.CompileFilter(u'SELECT field_a, SEPARATED BY field_b WHERE ') + + with self.assertRaises(errors.WrongPlugin): + test_filter.CompileFilter(u'SELECT field_a, field_b LIMIT WHERE') def testFilterFields(self): - query = 'SELECT stuff FROM machine WHERE some_stuff is "random"' - self.test_filter.CompileFilter(query) - self.assertEqual(['stuff'], self.test_filter.fields) - - query = 'SELECT stuff, a, b, date FROM machine WHERE some_stuff is "random"' - self.test_filter.CompileFilter(query) - self.assertEqual(['stuff', 'a', 'b', 'date'], self.test_filter.fields) - - query = 'SELECT date, message, zone, hostname WHERE some_stuff is "random"' - self.test_filter.CompileFilter(query) - self.assertEqual( - ['date', 'message', 'zone', 'hostname'], self.test_filter.fields) - - query = 'SELECT hlutir' - self.test_filter.CompileFilter(query) - self.assertEqual(['hlutir'], self.test_filter.fields) - - query = 'SELECT hlutir LIMIT 10' - self.test_filter.CompileFilter(query) - self.assertEqual(['hlutir'], self.test_filter.fields) - self.assertEqual(10, self.test_filter.limit) + test_filter = dynamic_filter.DynamicFilter() + + test_filter.CompileFilter( + u'SELECT stuff FROM machine WHERE some_stuff is "random"') + expected_fields = [u'stuff'] + self.assertEqual(test_filter.fields, expected_fields) + + test_filter.CompileFilter( + u'SELECT stuff, a, b, date FROM machine WHERE some_stuff is "random"') + expected_fields = [u'stuff', u'a', u'b', u'date'] + self.assertEqual(test_filter.fields, expected_fields) + + test_filter.CompileFilter( + u'SELECT date, message, zone, hostname WHERE some_stuff is "random"') + expected_fields = [u'date', u'message', u'zone', u'hostname'] + self.assertEqual(test_filter.fields, expected_fields) + + test_filter.CompileFilter(u'SELECT hlutir') + expected_fields = [u'hlutir'] + self.assertEqual(test_filter.fields, expected_fields) + + test_filter.CompileFilter(u'SELECT hlutir LIMIT 10') + expected_fields = [u'hlutir'] + self.assertEqual(test_filter.fields, expected_fields) + self.assertEqual(10, test_filter.limit) if __name__ == '__main__': diff --git a/tests/filters/event_filter.py b/tests/filters/event_filter.py new file mode 100644 index 0000000000..43ed71eb22 --- /dev/null +++ b/tests/filters/event_filter.py @@ -0,0 +1,41 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""Tests for the event object filter.""" + +import unittest + +from plaso.filters import event_filter +from plaso.lib import errors + +from tests.filters import test_lib + + +class EventObjectFilterTest(test_lib.FilterTestCase): + """Tests for the event object filter.""" + + def testCompilerFilter(self): + """Tests the CompileFilter function.""" + test_filter = event_filter.EventObjectFilter() + + test_filter.CompileFilter( + 'some_stuff is "random" and other_stuff is not "random"') + + with self.assertRaises(errors.WrongPlugin): + test_filter.CompileFilter( + u'SELECT stuff FROM machine WHERE conditions are met') + + with self.assertRaises(errors.WrongPlugin): + test_filter.CompileFilter( + u'/tmp/file_that_most_likely_does_not_exist') + + with self.assertRaises(errors.WrongPlugin): + test_filter.CompileFilter( + u'some random stuff that is destined to fail') + + with self.assertRaises(errors.WrongPlugin): + test_filter.CompileFilter( + u'some_stuff is "random" and other_stuff ') + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/filters/eventfilter.py b/tests/filters/eventfilter.py deleted file mode 100644 index 56dd2f196f..0000000000 --- a/tests/filters/eventfilter.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -import unittest - -from plaso.filters import eventfilter - -from tests.filters import test_lib - - -class EventObjectFilterTest(test_lib.FilterTestHelper): - """Tests for the EventObjectFilter filter.""" - - def setUp(self): - """Sets up the needed objects used throughout the test.""" - self.test_filter = eventfilter.EventObjectFilter() - - def testFilterFail(self): - """Run few tests that should not be a proper filter.""" - self.TestFail('SELECT stuff FROM machine WHERE conditions are met') - self.TestFail('/tmp/file_that_most_likely_does_not_exist') - self.TestFail('some random stuff that is destined to fail') - self.TestFail('some_stuff is "random" and other_stuff ') - - def testFilterApprove(self): - self.TestTrue('some_stuff is "random" and other_stuff is not "random"') - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/filters/filter_list.py b/tests/filters/filter_list.py new file mode 100644 index 0000000000..98a85cc981 --- /dev/null +++ b/tests/filters/filter_list.py @@ -0,0 +1,101 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""Tests for list of object filters.""" + +import os +import tempfile +import unittest + +from plaso.filters import filter_list +from plaso.lib import errors + +from tests.filters import test_lib + + +class ObjectFilterTest(test_lib.FilterTestCase): + """Tests for the list of object filters.""" + + def _CreateFileAndTest(self, test_filter, content): + """Creates a filter file and then runs the test. + + Args: + test_filter: the test filter object (instance of FilterObject). + content: the content of the filter file. + """ + # The temporary file needs to be closed to make sure the content + # was been written. + temporary_file_path = u'' + with tempfile.NamedTemporaryFile(delete=False) as temporary_file: + temporary_file_path = temporary_file.name + temporary_file.write(content) + + try: + test_filter.CompileFilter(temporary_file_path) + finally: + os.remove(temporary_file_path) + + def testCompilerFilter(self): + """Tests the CompileFilter function.""" + test_filter = filter_list.ObjectFilterList() + + with self.assertRaises(errors.WrongPlugin): + test_filter.CompileFilter( + u'SELECT stuff FROM machine WHERE conditions are met') + + with self.assertRaises(errors.WrongPlugin): + test_filter.CompileFilter( + u'/tmp/file_that_most_likely_does_not_exist') + + with self.assertRaises(errors.WrongPlugin): + test_filter.CompileFilter( + u'some random stuff that is destined to fail') + + with self.assertRaises(errors.WrongPlugin): + test_filter.CompileFilter( + u'some_stuff is "random" and other_stuff ') + + with self.assertRaises(errors.WrongPlugin): + test_filter.CompileFilter( + u'some_stuff is "random" and other_stuff is not "random"') + + def testFilterApprove(self): + test_filter = filter_list.ObjectFilterList() + + one_rule = u'\n'.join([ + u'Again_Dude:', + u' description: Heavy artillery caught on fire', + u' case_nr: 62345', + u' analysts: [anonymous]', + u' urls: [cnn.com,microsoft.com]', + u' filter: message contains "dude where is my car"']) + + self._CreateFileAndTest(test_filter, one_rule) + + collection = u'\n'.join([ + u'Rule_Dude:', + u' description: This is the very case I talk about, a lot', + u' case_nr: 1235', + u' analysts: [dude, jack, horn]', + u' urls: [mbl.is,visir.is]', + (u' filter: date > "2012-01-01 10:54:13" and parser not contains ' + u'"evtx"'), + u'', + u'Again_Dude:', + u' description: Heavy artillery caught on fire', + u' case_nr: 62345', + u' analysts: [smith, perry, john]', + u' urls: [cnn.com,microsoft.com]', + u' filter: message contains "dude where is my car"', + u'', + u'Third_Rule_Of_Thumb:', + u' description: Another ticket for another day.', + u' case_nr: 234', + u' analysts: [joe]', + u' urls: [mbl.is,symantec.com/whereevillies,virustotal.com/myhash]', + u' filter: evil_bit is 1']) + + self._CreateFileAndTest(test_filter, collection) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/filters/filterlist.py b/tests/filters/filterlist.py deleted file mode 100644 index cec22d817e..0000000000 --- a/tests/filters/filterlist.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -"""Tests for the PFilters filter.""" - -import os -import logging -import tempfile -import unittest - -from plaso.filters import filterlist - -from tests.filters import test_lib - - -class ObjectFilterTest(test_lib.FilterTestHelper): - """Tests for the ObjectFilterList filter.""" - - def setUp(self): - """Sets up the needed objects used throughout the test.""" - self.test_filter = filterlist.ObjectFilterList() - - def testFilterFail(self): - """Run few tests that should not be a proper filter.""" - self.TestFail('SELECT stuff FROM machine WHERE conditions are met') - self.TestFail('/tmp/file_that_most_likely_does_not_exist') - self.TestFail('some random stuff that is destined to fail') - self.TestFail('some_stuff is "random" and other_stuff ') - self.TestFail('some_stuff is "random" and other_stuff is not "random"') - - def CreateFileAndTest(self, content): - """Creates a file and then runs the test.""" - name = '' - with tempfile.NamedTemporaryFile(delete=False) as file_object: - name = file_object.name - file_object.write(content) - - self.TestTrue(name) - - try: - os.remove(name) - except (OSError, IOError) as exception: - logging.warning( - u'Unable to remove temporary file: {0:s} with error: {1:s}'.format( - name, exception)) - - def testFilterApprove(self): - one_rule = u'\n'.join([ - u'Again_Dude:', - u' description: Heavy artillery caught on fire', - u' case_nr: 62345', - u' analysts: [anonymous]', - u' urls: [cnn.com,microsoft.com]', - u' filter: message contains "dude where is my car"']) - - self.CreateFileAndTest(one_rule) - - collection = u'\n'.join([ - u'Rule_Dude:', - u' description: This is the very case I talk about, a lot', - u' case_nr: 1235', - u' analysts: [dude, jack, horn]', - u' urls: [mbl.is,visir.is]', - (u' filter: date > "2012-01-01 10:54:13" and parser not contains ' - u'"evtx"'), - u'', - u'Again_Dude:', - u' description: Heavy artillery caught on fire', - u' case_nr: 62345', - u' analysts: [smith, perry, john]', - u' urls: [cnn.com,microsoft.com]', - u' filter: message contains "dude where is my car"', - u'', - u'Third_Rule_Of_Thumb:', - u' description: Another ticket for another day.', - u' case_nr: 234', - u' analysts: [joe]', - u' urls: [mbl.is,symantec.com/whereevillies,virustotal.com/myhash]', - u' filter: evil_bit is 1']) - - self.CreateFileAndTest(collection) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/filters/manager.py b/tests/filters/manager.py new file mode 100644 index 0000000000..18f60443d7 --- /dev/null +++ b/tests/filters/manager.py @@ -0,0 +1,35 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""Tests for the event filters manager.""" + +import unittest + +from plaso.filters import manager + +from tests.filters import test_lib + + +class FiltersManagerTest(unittest.TestCase): + """Tests for the event filters manager.""" + + def testFilterRegistration(self): + """Tests the RegisterFilter and DeregisterFilter functions.""" + # pylint: disable=protected-access + number_of_filters = len(manager.FiltersManager._filter_classes) + + manager.FiltersManager.RegisterFilter(test_lib.TestEventFilter) + self.assertEqual( + len(manager.FiltersManager._filter_classes), + number_of_filters + 1) + + with self.assertRaises(KeyError): + manager.FiltersManager.RegisterFilter(test_lib.TestEventFilter) + + manager.FiltersManager.DeregisterFilter(test_lib.TestEventFilter) + self.assertEqual( + len(manager.FiltersManager._filter_classes), + number_of_filters) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/filters/test_lib.py b/tests/filters/test_lib.py index ee4922e1c7..603cb8cda7 100644 --- a/tests/filters/test_lib.py +++ b/tests/filters/test_lib.py @@ -1,38 +1,23 @@ # -*- coding: utf-8 -*- import unittest -from plaso.lib import errors +from plaso.filters import interface -# TODO: refactor this code: -# * change the name of the class to FilterTestCase -# * fix the issues masked by the pylint override +class TestEventFilter(interface.FilterObject): + """Class to define a filter for a test event.""" -# pylint: disable=redundant-unittest-assert -class FilterTestHelper(unittest.TestCase): - """A simple class that provides helper functions for testing filters.""" + def CompileFilter(self, unused_filter_expression): + """Compiles the filter expression. - def setUp(self): - """This should be overwritten.""" - self.test_filter = None + Args: + filter_expression: string that contains the filter expression. - def TestTrue(self, query): - """A quick test that should return a valid filter.""" - if not self.test_filter: - self.assertTrue(False) + Raises: + WrongPlugin: if the filter could not be compiled. + """ + pass - try: - self.test_filter.CompileFilter(query) - # And a success. - self.assertTrue(True) - except errors.WrongPlugin: - # Let the test fail. - self.assertTrue(False) - def TestFail(self, query): - """A quick failure test with a filter.""" - if not self.test_filter: - self.assertTrue(False) - - with self.assertRaises(errors.WrongPlugin): - self.test_filter.CompileFilter(query) +class FilterTestCase(unittest.TestCase): + """The unit test case for an event filter.""" diff --git a/tests/formatters/manager.py b/tests/formatters/manager.py index 9ca5f650dd..8335321429 100644 --- a/tests/formatters/manager.py +++ b/tests/formatters/manager.py @@ -8,8 +8,7 @@ from plaso.formatters import mediator from plaso.formatters import winreg # pylint: disable=unused-import -# TODO: refactor to event_test_lib. -from tests.lib import event as event_test +from tests.lib import event as event_test_lib from tests.formatters import test_lib @@ -18,7 +17,7 @@ class FormattersManagerTest(unittest.TestCase): def setUp(self): """Sets up the needed objects used throughout the test.""" - self._event_objects = event_test.GetEventObjects() + self._event_objects = event_test_lib.GetEventObjects() def testFormatterRegistration(self): """Tests the RegisterFormatter and DeregisterFormatter functions.""" diff --git a/tests/lib/objectfilter.py b/tests/lib/objectfilter.py index 192d311a65..6b1d865e60 100644 --- a/tests/lib/objectfilter.py +++ b/tests/lib/objectfilter.py @@ -4,6 +4,7 @@ import unittest +from plaso.lib import errors from plaso.lib import objectfilter @@ -338,7 +339,7 @@ def testEscaping(self): self.assertEqual(parser.args[0], '\n') # Invalid escape sequence. parser = objectfilter.Parser(r'a is "\z"') - with self.assertRaises(objectfilter.ParseError): + with self.assertRaises(errors.ParseError): parser.Parse() # Can escape the backslash. @@ -349,7 +350,7 @@ def testEscaping(self): # This fails as it's not really a hex escaped string. parser = objectfilter.Parser(r'a is "\xJZ"') - with self.assertRaises(objectfilter.ParseError): + with self.assertRaises(errors.ParseError): parser.Parse() # Instead, this is what one should write. @@ -370,55 +371,55 @@ def testParse(self): objectfilter.Parser('attribute == 1').Parse() objectfilter.Parser('attribute == 0x10').Parse() parser = objectfilter.Parser('attribute == 1a') - with self.assertRaises(objectfilter.ParseError): + with self.assertRaises(errors.ParseError): parser.Parse() objectfilter.Parser('attribute == 1.2').Parse() objectfilter.Parser('attribute == \'bla\'').Parse() objectfilter.Parser('attribute == "bla"').Parse() parser = objectfilter.Parser('something == red') - self.assertRaises(objectfilter.ParseError, parser.Parse) + self.assertRaises(errors.ParseError, parser.Parse) # Can't start with AND. parser = objectfilter.Parser('and something is \'Blue\'') - with self.assertRaises(objectfilter.ParseError): + with self.assertRaises(errors.ParseError): parser.Parse() # Test negative filters. parser = objectfilter.Parser('attribute not == \'dancer\'') - with self.assertRaises(objectfilter.ParseError): + with self.assertRaises(errors.ParseError): parser.Parse() parser = objectfilter.Parser('attribute == not \'dancer\'') - with self.assertRaises(objectfilter.ParseError): + with self.assertRaises(errors.ParseError): parser.Parse() parser = objectfilter.Parser('attribute not not equals \'dancer\'') - with self.assertRaises(objectfilter.ParseError): + with self.assertRaises(errors.ParseError): parser.Parse() parser = objectfilter.Parser('attribute not > 23') - with self.assertRaises(objectfilter.ParseError): + with self.assertRaises(errors.ParseError): parser.Parse() # Need to close braces. objectfilter.Parser('(a is 3)').Parse() parser = objectfilter.Parser('(a is 3') - self.assertRaises(objectfilter.ParseError, parser.Parse) + self.assertRaises(errors.ParseError, parser.Parse) # Need to open braces to close them. parser = objectfilter.Parser('a is 3)') - self.assertRaises(objectfilter.ParseError, parser.Parse) + self.assertRaises(errors.ParseError, parser.Parse) # Context Operator alone is not accepted. parser = objectfilter.Parser('@attributes') - with self.assertRaises(objectfilter.ParseError): + with self.assertRaises(errors.ParseError): parser.Parse() # Accepted only with braces. objectfilter.Parser('@attributes( name is \'adrien\')').Parse() # Not without them. parser = objectfilter.Parser('@attributes name is \'adrien\'') - with self.assertRaises(objectfilter.ParseError): + with self.assertRaises(errors.ParseError): parser.Parse() # Can nest context operators. diff --git a/tests/lib/pfilter.py b/tests/lib/pfilter.py index 3c79fe2f1f..a3656f0d90 100644 --- a/tests/lib/pfilter.py +++ b/tests/lib/pfilter.py @@ -7,7 +7,7 @@ from plaso.formatters import interface as formatters_interface from plaso.formatters import manager as formatters_manager from plaso.lib import event -from plaso.lib import objectfilter +from plaso.lib import errors from plaso.lib import pfilter from plaso.lib import timelib @@ -82,9 +82,7 @@ def testPlasoEvents(self): # as a positive one. query = 'filename not not contains \'GoodFella\'' my_parser = pfilter.BaseParser(query) - self.assertRaises( - objectfilter.ParseError, - my_parser.Parse) + self.assertRaises(errors.ParseError, my_parser.Parse) # Test date filtering. query = 'date >= \'2015-11-18\'' diff --git a/tools/log2timeline.py b/tools/log2timeline.py index c2106132d4..97f1b75dd8 100755 --- a/tools/log2timeline.py +++ b/tools/log2timeline.py @@ -145,6 +145,24 @@ def _FormatStatusTableRow( return u'{0:s}\t{1:d}\t{2:s}\t{3:s}\t{4:s}'.format( identifier, pid, status, events, display_name) + def _GetMatcher(self, filter_expression): + """Retrieves a filter object for a specific filter expression. + + Args: + filter_expression: string that contains the filter expression. + + Returns: + A filter object (instance of objectfilter.TODO) or None. + """ + try: + parser = pfilter.BaseParser(filter_expression).Parse() + return parser.Compile(pfilter.PlasoAttributeFilterImplementation) + + except errors.ParseError as exception: + logging.error( + u'Unable to create filter: {0:s} with error: {1:s}'.format( + filter_expression, exception)) + def _ParseOutputOptions(self, options): """Parses the output options. @@ -534,7 +552,7 @@ def ParseOptions(self, options): self._filter_expression = getattr(options, u'filter', None) if self._filter_expression: # TODO: refactor self._filter_object out the tool into the frontend. - self._filter_object = pfilter.GetMatcher(self._filter_expression) + self._filter_object = self._GetMatcher(self._filter_expression) if not self._filter_object: raise errors.BadConfigOption( u'Invalid filter expression: {0:s}'.format(self._filter_expression)) diff --git a/tools/psort.py b/tools/psort.py index af5928f11c..426be823b5 100755 --- a/tools/psort.py +++ b/tools/psort.py @@ -15,12 +15,14 @@ import sys import time -from plaso import filters +# The following import makes sure the filters are registered. +from plaso import filters # pylint: disable=unused-import # TODO: remove after psort options refactor. from plaso.analysis import interface as analysis_interface from plaso.cli import analysis_tool from plaso.cli import tools as cli_tools from plaso.cli.helpers import manager as helpers_manager +from plaso.filters import manager as filters_manager from plaso.frontend import psort from plaso.output import interface as output_interface from plaso.output import manager as output_manager @@ -117,7 +119,8 @@ def _ParseFilterOptions(self, options): """ self._filter_expression = getattr(options, u'filter', None) if self._filter_expression: - self._filter_object = filters.GetFilter(self._filter_expression) + self._filter_object = filters_manager.FiltersManager.GetFilterObject( + self._filter_expression) if not self._filter_object: raise errors.BadConfigOption( u'Invalid filter expression: {0:s}'.format(self._filter_expression))