# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one # or more contributor license agreements. Licensed under the Elastic License # 2.0; you may not use this file except in compliance with the Elastic License # 2.0. import operator import re import eql.ast from eql import Walker, EqlCompileError, utils from eql.functions import CidrMatch from .errors import KqlRuntimeError, KqlCompileError class FilterGenerator(Walker): __cidr_cache = {} def _walk_default(self, node, *args, **kwargs): raise KqlCompileError("Unable to convert {}".format(node)) @classmethod def equals(cls, term, value): if utils.is_string(term) and utils.is_string(value): if CidrMatch.ip_compiled.match(term) and CidrMatch.cidr_compiled.match(value): # check for an ipv4 cidr if value not in cls.__cidr_cache: cls.__cidr_cache[value] = CidrMatch.get_callback(None, eql.ast.String(value)) return cls.__cidr_cache[value](term) return term == value @classmethod def get_terms(cls, document, path): if isinstance(document, (tuple, list)): for d in document: for term in cls.get_terms(d, path): yield term elif isinstance(document, dict): document = document.get(path[0]) path = path[1:] if len(path) > 0: for term in cls.get_terms(document, path): yield term elif isinstance(document, (tuple, list)): for term in document: yield term elif document is not None: yield document def _walk_value(self, tree, compare_function=None): value = tree.value compare_function = compare_function or self.equals def check_value(term): if term is None: return False if isinstance(term, list): return any(check_value(t) for t in term) if isinstance(term, (bool, float, int)) or utils.is_string(term): v = value if utils.is_string(v) and isinstance(term, (bool, int, float)): if isinstance(v, bool): v = v == "false" if isinstance(term, int): v = int(v) elif isinstance(v, float): v = float(v) elif utils.is_string(term) and isinstance(v, (bool, int, float)): v = utils.to_unicode(v) return compare_function(term, v) else: raise KqlRuntimeError("Cannot compare value {}".format(term)) return check_value def _walk_exists(self, _): return lambda terms: any(t is not None for t in terms) def _walk_wildcard(self, tree): pattern = tree.value regex = re.compile(".*?".join(map(re.escape, pattern.split("*"))), re.UNICODE | re.DOTALL) return lambda terms: any(t is not None and regex.fullmatch(t) for t in terms) def _walk_field(self, field): path = field.name.split(".") get_terms = self.get_terms def callback(document): terms = get_terms(document, path) terms = list(terms) return terms return callback def _walk_field_range(self, tree): field = self.walk(tree.field) operators = {"<": operator.lt, "<=": operator.le, ">=": operator.ge, ">": operator.gt} check_range = self.walk(tree.value, operators[tree.operator]) return lambda doc: check_range(field(doc)) def _walk_nested_query(self, tree): field = self.walk(tree.field) expr = self.walk(tree.expr) def check_nested(doc): doc = field(doc) if isinstance(doc, dict): return expr(doc) elif isinstance(doc, (list, tuple)): return any(expr(d) for d in doc) return check_nested def _walk_list(self, trees, reduce_function, *args, **kwargs): walked = [self.walk(item, *args, **kwargs) for item in trees.items] return lambda x: reduce_function(item(x) for item in walked) def _walk_not_expr(self, tree): expr = self.walk(tree.expr) return lambda doc: not expr(doc) def _walk_and_expr(self, tree): return self._walk_list(tree, all) def _walk_or_expr(self, tree): return self._walk_list(tree, any) def _walk_and_values(self, tree): return self._walk_list(tree, all) def _walk_or_values(self, tree): return self._walk_list(tree, any) def _walk_not_value(self, tree): expr = self.walk(tree.value) return lambda value: not expr(value) def _walk_field_comparison(self, tree): field = self.walk(tree.field) value = self.walk(tree.value) return lambda doc: value(field(doc)) @classmethod def filter(cls, expression): return cls().walk(expression)