MapPattern
This transformer will take in a value and transform it based on multiple condition expressions (wildcard, regex, etc) defined in a JSON dictionary structure. The key:value pair of the JSON dictionary should be: "condition expression": "desired outcome" For example: { ".*match 1.*": "Dest Val1", ".*match 2.*": "Dest Val2", ".*match 3(.*)": "\\1", "*match 4*": { "algorithm": "wildcard", "output": "Dest Val4" } } The transformer will return the value matched to a pattern following to the priority. When unmatched or the input value is structured (dict or list), it will simply return the input value.
python · Filters And Transformers
Source
import fnmatch import json import re from collections.abc import Callable, Generator from typing import Any import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 DEFAULT_ALGORITHM = "literal" DEFAULT_PRIORITY = "first_match" def demisto_get(obj: Any, path: Any) -> Any: """ demisto.get(), this supports a syntax of path escaped with backslash. """ def split_context_path(path: str) -> list[str]: nodes = [] node = [] itr = iter(path) for c in itr: if c == "\\": try: node.append(next(itr)) except StopIteration: node.append("\\") elif c == ".": nodes.append("".join(node)) node = [] else: node.append(c) nodes.append("".join(node)) return nodes if not isinstance(obj, dict): return None for part in split_context_path(path): if obj and part in obj: obj = obj[part] else: return None return obj def make_regex(pattern: str, algorithm: str) -> str: """Transform a pattern to a regex pattern. Supported algorithms; - literal - wildcard - regex - regmatch :param pattern: The pattern to be transformed. :param algorithm: The algorithm for `pattern`. :return: An regex pattern created. """ if algorithm == "literal": return re.escape(pattern) elif algorithm == "wildcard": return fnmatch.translate(pattern) elif algorithm in ("regex", "regmatch"): return pattern else: raise ValueError(f"Invalid algorithm: {algorithm}") def expand_match(match: re.Match, value: Any) -> Any: """Return the value obtained by doing backslash substitution on the template string template :param match: The match object. :param value: The template value. :return: The value replaced. """ if isinstance(value, dict): return {expand_match(match, k): expand_match(match, v) for k, v in value.items()} elif isinstance(value, list): return [expand_match(match, v) for v in value] elif isinstance(value, str): return match.expand(value.replace(r"\0", r"\g<0>")) else: return value class Mapping: def __init__(self, pattern: str, repl: str | dict[str, Any]): """ :param pattern: The pattern to compare to the value. :param repl: The parameters for pattern matching or making outputs. """ repl = repl if isinstance(repl, dict) else {"output": repl} exclude = repl.get("exclude") or [] self.pattern: str = pattern self.exclude: list[str] = exclude if isinstance(exclude, list) else [exclude] self.output: Any = repl.get("output") self.algorithm: str | None = repl.get("algorithm") self.next: Any = repl.get("next") self.ignore_syntax = bool(repl.get("ignore_syntax") or False) def iterate_pattern_mapping(pattern_mapping: list[dict[str, Any]] | dict[str, Any]) -> Generator[Mapping, None, None]: """Iterate mapping entry. :param pattern_mapping: The pattern mapping table. :return: Each mapping entry. {pattern:, exclude:, algorithm:, output:, next:} """ if isinstance(pattern_mapping, list): for m in pattern_mapping: yield from iterate_pattern_mapping(m) elif isinstance(pattern_mapping, dict): for pattern, repl in pattern_mapping.items(): yield Mapping(pattern, repl) else: raise ValueError(f"pattern-mapping must be an array or an object: {pattern_mapping}") class ContextData: def __init__(self, context: Any = None, arg_value: dict[str, Any] | None = None): """ :param context: The demisto context. :param arg_value: The data of the `value` given in the argument parameters. """ self.__demisto = context self.__value = arg_value def get(self, key: str | None, node: Any | None = None, ignore_errors: bool = False) -> Any: """Get the context value given the key :param key: The dt expressions (string within ${}). :param node: The current node. :param ignore_errors: Set to True to ignore errors, otherwise False. :return: The value. """ if key is not None: dx = self.__demisto if key != ".." and not key.startswith("..=") and key.startswith(".."): dx = node key = key[2:] elif key != "." and not key.startswith(".=") and key.startswith("."): dx = self.__value key = key[1:] if not key or key == ".": return dx try: return demisto.dt(dx, key) except Exception: if not ignore_errors: raise return None class Formatter: def __init__(self, start_marker: str, end_marker: str, keep_symbol_to_null: bool): if not start_marker: raise ValueError("start-marker is required.") self.__start_marker = start_marker self.__end_marker = end_marker self.__keep_symbol_to_null = keep_symbol_to_null @staticmethod def __is_end_mark(source: str, ci: int, end_marker: str) -> bool: if end_marker: return source[ci : ci + len(end_marker)] == end_marker else: c = source[ci] if c.isspace(): return True elif c.isascii(): return c != "_" and not c.isalnum() else: return False def __extract( self, source: str, extractor: Callable[[str, ContextData | None, dict[str, Any] | None], Any] | None, dx: ContextData | None, node: dict[str, Any] | None, si: int, markers: tuple[str, str] | None, ) -> tuple[Any, int | None]: """Extract a template text, or an enclosed value within starting and ending marks :param source: The template text, or the enclosed value starts with the next charactor of a start marker :param extractor: The function to extract an enclosed value as DT :param dx: The context data :param node: The current node :param si: The index of `source` to start extracting :param markers: The start and end marker to find an end position for parsing an enclosed value. It must be None when the template text is given to `source`. :return: The extracted value and index of `source` when parsing ended. The index is the next after the end marker when extracting the enclosed value. """ out = None ci = si while ci < len(source): if markers is not None and Formatter.__is_end_mark(source, ci, markers[1]): key = source[si:ci] if out is None else str(out) + source[si:ci] if extractor: if (xval := extractor(key, dx, node)) is None and self.__keep_symbol_to_null: xval = markers[0] + key + markers[1] else: xval = key return xval, ci + len(markers[1]) elif extractor and source[ci : ci + len(self.__start_marker)] == self.__start_marker: xval, ei = self.__extract( source, extractor, dx, node, ci + len(self.__start_marker), (self.__start_marker, self.__end_marker) ) if si != ci: out = source[si:ci] if out is None else str(out) + source[si:ci] if ei is None: xval = self.__start_marker ei = ci + len(self.__start_marker) if out is None: out = xval elif xval is not None: out = str(out) + str(xval) si = ci = ei elif markers is None: ci += 1 elif endc := {"(": ")", "{": "}", "[": "]", '"': '"', "'": "'"}.get(source[ci]): _, ei = self.__extract(source, None, dx, node, ci + 1, (source[ci], endc)) ci = ci + 1 if ei is None else ei elif source[ci] == "\\": ci += 2 else: ci += 1 if markers is not None: # unbalanced braces, brackets, quotes, etc. return None, None elif not extractor: return None, ci elif si >= len(source): return out, ci elif out is None: return source[si:], ci else: return str(out) + source[si:], ci def build( self, template: Any, extractor: Callable[[str, ContextData | None, dict[str, Any] | None], Any] | None, dx: ContextData | None, node: dict[str, Any] | None, ) -> Any: """Format a text from a template including DT expressions :param template: The template. :param extractor: The extractor to get real value within ${dt}. :param dx: The context instance. :param node: The current node. :return: The text built from the template. """ if isinstance(template, dict): return {self.build(k, extractor, dx, node): self.build(v, extractor, dx, node) for k, v in template.items()} elif isinstance(template, list): return [self.build(v, extractor, dx, node) for v in template] elif isinstance(template, str): return self.__extract(template, extractor, dx, node, 0, None)[0] if template else "" else: return template def extract_value(source: Any, dx: ContextData | None, node: dict[str, Any] | None = None) -> Any: """Extract value including dt expression :param source: The value to be extracted that may include dt expressions. :param dx: The demisto context. :param node: The current node. :return: The value extracted. """ def __extract_dt(dtstr: str, dx: ContextData | None, node: dict[str, Any] | None = None) -> Any: try: return dx.get(dtstr, node) if dx else dtstr except Exception as err: demisto.debug(f'failed to extract dt from "{dtstr=}". Error: {err}') return None return Formatter("${", "}", False).build(source, __extract_dt, dx, node) class Translator: def __init__(self, context: Any, arg_value: Any, fields_comp_mode: bool, wildcards: list[str], regex_flags: int): """ :param context: The demisto context. :param arg_value: The data of the `value` given in the argument parameters. :param fields_comp_mode: True - Fields comp mode, otherwise False. :param wildcards: The list of the special patterns which match to any value regardless of algorithm. :param regex_flags: The regex flags for pattern matching. """ self.__arg_value = arg_value self.__demisto = context self.__fields_comp_mode = fields_comp_mode self.__wildcards = wildcards self.__regex_flags = regex_flags self.__context = ContextData(context=context, arg_value=arg_value) def __match( self, algorithm: str, pattern: str, value: Any, exclusions: list[str], ignore_syntax: bool = False ) -> bool | re.Match: """Perform the pattern matching. Supported algorithms: - literal - wildcard - regex - regmatch - dt :param algorithm: The algorithm for pattern match. :param pattern: The pattern to compare to the value. :param value: The value to compare to the pattern. :param exclusions: The list of the patterns to exclude matching results. :param ignore_syntax: Set to True to ignore syntax errors to the pattern, False otherwise. :return: False - unmatched. Returns True for matched pattern when literal, wildcard, regmatch and dt is given to the algorithm. Return re.Match for matched mattern pattern when regex is given to it. Note: Returns True if the value matched to any of special wildcard patterns even in regex. """ if algorithm == "literal": if isinstance(value, dict | list): return False value = "" if value is None else str(value) if pattern not in self.__wildcards: if (self.__regex_flags & re.IGNORECASE) != 0: if pattern.lower() != value.lower(): return False else: if pattern != value: return False if any(x == value for x in exclusions): return False elif algorithm in ("wildcard", "regex", "regmatch"): if isinstance(value, dict | list): return False value = "" if value is None else str(value) regex_match = None if pattern not in self.__wildcards: try: regex = make_regex(pattern, algorithm) except (AttributeError, ValueError): if not ignore_syntax: raise return False regex_match = re.fullmatch(regex, value, flags=self.__regex_flags) if not regex_match: return False if any(re.fullmatch(make_regex(x, algorithm), value, flags=self.__regex_flags) for x in exclusions): return False if algorithm == "regex" and isinstance(regex_match, re.Match): return regex_match elif algorithm == "dt": if pattern not in self.__wildcards and not self.__context.get(pattern, value, ignore_errors=ignore_syntax): return False if any(self.__context.get(x, value, ignore_errors=ignore_syntax) for x in exclusions): return False else: raise ValueError(f"This function only supports literal, wildcard and dt: {algorithm}") return True def translate( self, source: Any, pattern_mapping: list[dict[str, Any]] | dict[str, Any], priority: str, algorithm: str ) -> tuple[Any, bool]: """Replace the string given with the patterns. :param source: The string to be replaced. :param pattern_mapping: The mapping table to translate. :param priority: The priority order (first_match, last_match or longest_pattern). :param algorithm: The default algorithm for pattern match. :return: The new value replaced by a mapping, and a flag if a pattern has matched or not. """ matched = False matched_output = source for mapping in iterate_pattern_mapping(pattern_mapping): algorithm = mapping.algorithm or algorithm # Check if the source matches a pattern source_match = self.__match( algorithm=algorithm, pattern=mapping.pattern, value=source, exclusions=mapping.exclude, ignore_syntax=mapping.ignore_syntax, ) if not source_match: continue # Set the output fields_comp_mode = False output = mapping.output if output is None: output = self.__arg_value if mapping.next and isinstance(output, dict): fields_comp_mode = self.__fields_comp_mode elif algorithm == "regex" and isinstance(source_match, re.Match): output = expand_match(source_match, output) if self.__demisto is not None: # Extract values only if `context` of the arguments is given. output = extract_value(output, self.__context, source) if mapping.next: if fields_comp_mode: output, matched = self.translate_fields( obj_value=output, field_mapping=mapping.next, priority=priority, algorithm=algorithm ) else: output, matched = self.translate( source=output, pattern_mapping=mapping.next, priority=priority, algorithm=algorithm ) if not matched: continue if priority in ("first_match", "last_match"): matched = True matched_output = output if priority == "first_match": break else: raise ValueError(f"Invalid priority: {priority}") return matched_output, matched def translate_fields( self, obj_value: dict[str, Any], field_mapping: dict[str, Any], priority: str, algorithm: str ) -> tuple[Any, bool]: """Replace the string given with the field mapping. :param obj_value: The object whose values to be replaced. :param field_mapping: The mapping table to translate. :param priority: The priority order (first_match, last_match or longest_pattern). :param algorithm: The default algorithm for pattern match. :return: The new value replaced by a mapping, and a flag if a pattern has matched or not. """ if not isinstance(field_mapping, dict): raise ValueError(f"field-mapping must be an array or an object in JSON: type={type(field_mapping)}") for path, mapping in field_mapping.items(): if not isinstance(mapping, dict | list): raise ValueError(f"pattern-mapping must be an array or an object in JSON: type={type(mapping)}") # Get a value for pattern matching comparison_value = demisto_get(obj_value, path) matched_output, matched = self.translate(comparison_value, mapping, priority, algorithm) if matched: return matched_output, matched return obj_value, False def main(): args = demisto.args() value = args.get("value") try: mappings = args.get("mappings") or {} algorithm = args.get("algorithm") or DEFAULT_ALGORITHM priority = args.get("priority") or DEFAULT_PRIORITY context = args.get("context") fields_comp_mode = argToBoolean(args.get("compare_fields") or "false") wildcards = argToList(args.get("wildcards")) default_value = args.get("default_value") regex_flags = re.IGNORECASE if argToBoolean(args.get("caseless") or "true") else 0 for flag in argToList(args.get("flags", "")): if flag in ("dotall", "s"): regex_flags |= re.DOTALL elif flag in ("multiline", "m"): regex_flags |= re.MULTILINE elif flag in ("ignorecase", "i"): regex_flags |= re.IGNORECASE elif flag in ("unicode", "u"): regex_flags |= re.UNICODE else: raise ValueError(f"Unknown flag: {flag}") if isinstance(mappings, str): try: mappings = json.loads(mappings) except ValueError: raise ValueError(f"Unable to decode mappings in JSON: {mappings}") tr = Translator( context=context, arg_value=value, fields_comp_mode=fields_comp_mode, wildcards=wildcards, regex_flags=regex_flags ) matched = False if fields_comp_mode: if isinstance(value, dict): value, matched = tr.translate_fields( obj_value=value, field_mapping=mappings, priority=priority, algorithm=algorithm ) else: value, matched = tr.translate(source=value, pattern_mapping=mappings, priority=priority, algorithm=algorithm) if default_value and not matched: value = default_value except Exception as err: # Don't return an error by return_error() as this is transformer. raise DemistoException(str(err)) return_results(value) if __name__ in ("__builtin__", "builtins", "__main__"): main()
README
This transformer will take in a value and transform it based on multiple condition expressions (wildcard, regex, etc) defined in a JSON dictionary structure. The key:value pair of the JSON dictionary should be:
“condition expression”: “desired outcome”
For example:
{
".*match 1.*": "Dest Val1",
".*match 2.*": "Dest Val2",
".*match 3(.*)": "\\1",
"*match 4*": {
"algorithm": "wildcard",
"output": "Dest Val4"
}
}
The transformer will return the value matched to a pattern following to the priority.
When unmatched or the input value is structured (dict or list), it will simply return the input value.
Script Data
| Name | Description |
|---|---|
| Script Type | python3 |
| Tags | transformer, string |
Inputs
| Argument Name | Description |
|---|---|
| value | The value to modify. |
| mappings | A JSON dictionary or list of it that contains key:value pairs that represent the “Condition”:”Outcome”. |
| algorithm | The default algorithm for pattern match. Available algorithm: literal, wildcard, regex, regmatch and dt. |
| caseless | Set to true for caseless comparison, false otherwise. |
| priority | The option to choose which value matched to return. Available options: first_match (default) and last_match. |
| context | The context: Input . (single dot) on `From previous tasks` to enable to extract the context data. |
| flags | The comma separated flags for pattern matching in regex. dotall (s), multiline (m), ignorecase (i) and unicode (u) are supported. This will apply to all the algorithms. |
| compare_fields | Set to true if you want pattern matching for each field, otherwise false. |
| wildcards | The list of the special patterns which match to any values regardless of algorithm. |
| default_value | The value to return when all the patterns are not satisfied. |
Outputs
There are no outputs for this script.
Syntax for mappings
mappings ::= pattern-mapping | field-mapping
# `field-mapping` must be used when you set `compare_fields` to true. `pattern-mapping` is used if it is not set.
pattern-mapping ::= list-pattern-mapping | base-pattern-mapping
list-pattern-mapping ::= List[base-pattern-mapping]
base-pattern-mapping ::= Dict[pattern, repl]
field-mapping ::= Dict[field-name, pattern-mapping]
field-name ::= str
pattern ::= str # The pattern string which depends on the algorithm given to match with the value.
repl ::= output-str | config
output-str ::= str # The data to replace to the value.
# - Backslash substitution on the template string is available in `regex`
# - DT syntax (${}) is available when `context` is enabled.
# - As DT syntax, `${..}` refers the value given in the inputs, and `${.<name>}` also refers the property located at the relateve path to it.
# `${...}` refers the value being evaluated, and `${..<name>}` also refers the property located at the relateve path to it.
output-any ::= output-str | Any # The data to replace to the value.
# `null` is the special value to identify the input value given in this transformer.
algorithm ::= "literal" | "wildcard" | "regex" | "regmatch" | "dt"
comp-fields ::= List[field] | comma-separated-fields
comma-separated-fields ::= str # Comma separated field
config ::= Dict[str, Any]
The structure is:
{
"algorithm": algorithm, # (Optional) The algorithm to pattern matching.
"output": output-any, # (Optional) The data to replace to the value by the pattern.
"exclude": pattern | List[pattern], # (Optional) Patterns to exclude in the pattern matching.
"ignore_syntax": bool # (Optional) Set to true if you want to ignore syntax errors to the pattern.
"next": mappings # (Optional) Subsequent conditions to do the pattern matching with the value taken from the output.
}
Pattern Matching
When you choose the dt as the algorithm, the value generated by a DT is handled as unmatched when it is considered as false in boolean condition in python, otherwise it is handles as matched.
In python, null, boolean False, integer 0, empty string, empty list and empty dict are considered as false.
Examples
Transform a severity name to the corresponding number.
algorithm: regmatch
caseless: true
priority: first_match
context:
flags:
compare_fields:
wildcards:
mappings
{
"Unknown": 0,
"Informational|Info": 0.5,
"Low": 1,
"Medium": 2,
"High": 3,
"Critical": 4
}
| Input | Output |
|---|---|
| High | 3 |
| Informational | 1 |
| Info | 1 |
| Abc | Abc |
Normalize a human readable phrase to a cannonical name.
algorithm: wildcard
caseless: true
priority: first_match
context:
flags:
compare_fields:
wildcards:
mappings
{
"*Low*": "low",
"*Medium*": "medium",
"*High*": "high",
"*": "unknown"
}
| Input | Output |
|---|---|
| 1 - Low | low |
| Medium | medium |
| high (3) | high |
| infomation | unknown |
Remove all the heading “Re:” or “Fw:” from an email subject.
algorithm: regex
caseless: true
priority: first_match
context:
flags:
compare_fields:
wildcards:
mappings
{
"( *(Re: *|Fw: *)*)(.*)": "\\3"
}
| Input | Output |
|---|---|
| Re: Re: Fw: Hello! | Hello! |
| Hello! | Hello! |
Extract the user name field from an text in an Active Directory user account format.
algorithm: regex
caseless: true
priority: first_match
context:
flags:
compare_fields:
wildcards:
mappings
{
"([^@]+)@.+": "\\1",
"[^\\\\]+\\\\(.+)": "\\1",
"[a-zA-Z_]([0-9a-zA-Z\\.-_]*)": null,
".*": "<unknown>"
}
| Input | Output |
|---|---|
| username@domain | username |
| domain\username | username |
| username | username |
| 012abc$ | <unknown> |
Extract the user name field from an quoted text in an Active Directory user account format.
algorithm: regex
caseless: true
priority: first_match
context:
flags:
compare_fields:
wildcards:
mappings
{
"\"(.*)\"": {
"output": "\\1",
"next": {
"([^@]+)@.+": "\\1",
"[^\\\\]+\\\\(.+)": "\\1",
"[a-zA-Z_]([0-9a-zA-Z\\.-_]*)": "\\0",
".*": "<unknown>"
}
},
"([^@]+)@.+": "\\1",
"[^\\\\]+\\\\(.+)": "\\1",
"[a-zA-Z_]([0-9a-zA-Z\\.-_]*)": null,
".*": "<unknown>"
}
| Input | Output |
|---|---|
| “username@domain” | username |
| username@domain | username |
| “domain\username” | username |
| domain\username | username |
| “username” | username |
| username | username |
| 012abc$ | <unknown> |
Extract first name and last name from an email address in firstname.lastname@domain, but the format is lastname.firstname@domain in some particular domains.
algorithm: regex
caseless: true
priority: first_match
context:
flags:
compare_fields:
wildcards:
mappings
[
{
"([^.]+)\\.([^@]+)@.+": {
"exclude": ".*@example2.com",
"output": "\\1 \\2"
}
},
{
"([^.]+)\\.([^@]+)@.+": "\\2 \\1",
"([^@]+)@.+": "\\1"
}
]
| Input | Output |
|---|---|
| john.doe@example1.com | john doe |
| doe.john@example2.com | john doe |
| username@example1.com | username |
Normalize a date/time text to YYYY-MM-DD HH:mm:ss TZ.
algorithm: regex
caseless: true
priority: first_match
context:
flags:
compare_fields:
wildcards:
mappings
{
"(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(\\.\\d+)?Z": "\\1-\\2-\\3 \\4:\\5:\\6 GMT",
"[^,]+, (\\d{1,2}) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": {
"output": "\\2",
"next": {
"Jan": {
"output": null,
"next": {
"[^,]+, (\\d) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-01-0\\1 \\4:\\5:\\6 \\7",
"[^,]+, (\\d{2}) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-01-\\1 \\4:\\5:\\6 \\7"
}
},
"Feb": {
"output": null,
"next": {
"[^,]+, (\\d) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-02-0\\1 \\4:\\5:\\6 \\7",
"[^,]+, (\\d{2}) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-02-\\1 \\4:\\5:\\6 \\7"
}
},
"Mar": {
"output": null,
"next": {
"[^,]+, (\\d) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-03-0\\1 \\4:\\5:\\6 \\7",
"[^,]+, (\\d{2}) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-03-\\1 \\4:\\5:\\6 \\7"
}
},
"Apr": {
"output": null,
"next": {
"[^,]+, (\\d) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-04-0\\1 \\4:\\5:\\6 \\7",
"[^,]+, (\\d{2}) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-04-\\1 \\4:\\5:\\6 \\7"
}
},
"May": {
"output": null,
"next": {
"[^,]+, (\\d) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-05-0\\1 \\4:\\5:\\6 \\7",
"[^,]+, (\\d{2}) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-05-\\1 \\4:\\5:\\6 \\7"
}
},
"Jun": {
"output": null,
"next": {
"[^,]+, (\\d) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-06-0\\1 \\4:\\5:\\6 \\7",
"[^,]+, (\\d{2}) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-06-\\1 \\4:\\5:\\6 \\7"
}
},
"Jul": {
"output": null,
"next": {
"[^,]+, (\\d) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-07-0\\1 \\4:\\5:\\6 \\7",
"[^,]+, (\\d{2}) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-07-\\1 \\4:\\5:\\6 \\7"
}
},
"Aug": {
"output": null,
"next": {
"[^,]+, (\\d) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-08-0\\1 \\4:\\5:\\6 \\7",
"[^,]+, (\\d{2}) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-08-\\1 \\4:\\5:\\6 \\7"
}
},
"Sep": {
"output": null,
"next": {
"[^,]+, (\\d) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-09-0\\1 \\4:\\5:\\6 \\7",
"[^,]+, (\\d{2}) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-09-\\1 \\4:\\5:\\6 \\7"
}
},
"Oct": {
"output": null,
"next": {
"[^,]+, (\\d) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-10-0\\1 \\4:\\5:\\6 \\7",
"[^,]+, (\\d{2}) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-10-\\1 \\4:\\5:\\6 \\7"
}
},
"Nov": {
"output": null,
"next": {
"[^,]+, (\\d) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-11-0\\1 \\4:\\5:\\6 \\7",
"[^,]+, (\\d{2}) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-11-\\1 \\4:\\5:\\6 \\7"
}
},
"Dec": {
"output": null,
"next": {
"[^,]+, (\\d) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-12-0\\1 \\4:\\5:\\6 \\7",
"[^,]+, (\\d{2}) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": "\\3-12-\\1 \\4:\\5:\\6 \\7"
}
}
}
}
}
| Input | Output |
|---|---|
| 2021-01-02T01:23:45.010Z | 2021-01-02 01:23:45 GMT |
| 2021-01-02T01:23:45Z | 2021-01-02 01:23:45 GMT |
| Tue, 3 Jun 2008 11:05:30 GMT | 2008-06-03 11:05:30 GMT |
Normalize a date/time text to YYYY-MM-DD HH:mm:ss TZ.
algorithm: regex
caseless: true
priority: first_match
context: . [From previous tasks]
flags:
compare_fields:
wildcards:
mappings
{
"(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(\\.\\d+)?Z": "\\1-\\2-\\3 \\4:\\5:\\6 GMT",
"[^,]+, (\\d{1,2}) ([^ ]+) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([^ ]+)": {
"output": {
"year": "\\3",
"month": "${.={Jan:'01', Feb: '02', Mar:'03', Apr:'04', May:'05', Jun:'06', Jul:'07', Aug:'08', Sep:'09', Oct:'10', Nov:'11', Dec:'12'}['\\2']}",
"day": "${.=('0'+'\\1').slice(-2)}",
"hour": "\\4",
"minute": "\\5",
"second": "\\6",
"tz": "\\7"
},
"next": {
"..month=val > 0": {
"algorithm": "dt",
"output": "${..year}-${..month}-${..day} ${..hour}:${..minute}:${..second} ${..tz}"
}
}
}
}
| Input | Output |
|---|---|
| 2021-01-02T01:23:45.010Z | 2021-01-02 01:23:45 GMT |
| 2021-01-02T01:23:45Z | 2021-01-02 01:23:45 GMT |
| Tue, 3 Jun 2008 11:05:30 GMT | 2008-06-03 11:05:30 GMT |
Pattern matching for different nodes
algorithm: wildcard
caseless: true
priority: first_match
context:
flags:
compare_fields: true
wildcards:
mappings
{
"IP": {
"127.*": "localhost"
},
"Host": {
"localhost": "localhost",
"*.local": "localhost",
"*": "other"
}
}
| Input | Output |
|---|---|
| {“IP”: “127.0.0.1”} | “localhost” |
| {“Host”: “localhost”} | “localhost” |
| {“Host”: “paloaltonetworks.local”} | “localhost” |
| {“IP”: “192.168.1.1”} | “other” |
Make a text with the value field corresponding to the score field.
algorithm: regex
caseless: true
priority: first_match
context: . [From previous tasks]
flags:
compare_fields: true
wildcards: *
mappings
{
"score": {
"1": "low - ${.value}",
"2": "medium - ${.value}",
"3": "high - ${.value}",
"*": "unknown - ${.value}"
}
}
| Input | Output |
|---|---|
| {“score”: 1, “value”: “192.168.1.1”} | “low - 192.168.1.1” |
| {“score”: 4, “value”: “192.168.1.1”} | “unknown - 192.168.1.1” |
Make a text with the value field corresponding to the score field.
algorithm: dt
caseless:
priority: first_match
context: . [From previous tasks]
flags:
compare_fields: true
wildcards: *
mappings
{
"score": {
"...=val < 30": "low - ${.value}",
"...=val < 50": "medium - ${.value}",
"...=val >= 50": "high - ${.value}",
"*": "unknown - ${.value}"
}
}
| Input | Output |
|---|---|
| {“score”: 10, “value”: “192.168.1.1”} | “low - 192.168.1.1” |
| {“score”: 40, “value”: “192.168.1.1”} | “medium - 192.168.1.1” |
| {“score”: 70, “value”: “192.168.1.1”} | “high - 192.168.1.1” |
| {“score”: “x”, “value”: “192.168.1.1”} | “unknown - 192.168.1.1” |
Make a phrase based on the values of score and type.
algorithm: dt
caseless:
priority: first_match
context: . [From previous tasks]
flags:
compare_fields: true
wildcards: *
mappings
{
"score": {
"...=val < 30": {
"next": {
"type": {
"IP": {
"algorithm": "literal",
"output": "benign IP"
},
"*": "low"
}
}
},
"...=val < 50": {
"next": {
"type": {
"IP": {
"algorithm": "literal",
"output": "suspicious IP"
},
"*": "medium"
}
}
},
"...=val >= 50": {
"next": {
"type": {
"IP": {
"algorithm": "literal",
"output": "malicious IP"
},
"*": "high"
}
}
},
"*": "unknown - ${.value}"
}
}
| Input | Output |
|---|---|
| {“score”: 70, “value”: “192.168.1.1”, “type”: “IP”} | “malicious IP” |
| {“score”: 10, “value”: “paloaltonetworks.com”, “type”: “domain”} | “low” |
| {“score”: “x”, “value”: “192.168.1.1”} | “unknown - 192.168.1.1” |
Check if the date is a leap day.
algorithm: regex
caseless:
priority: first_match
context:
flags:
compare_fields:
wildcards:
mappings
{
"(Jan|Mar|May|Jul|Aug|Oct|Dec) (\\d\\d?), \\d{4}": {
"output": "\\2",
"next": {
"...=val <= 31": {
"algorithm": "dt",
"output": false
}
}
},
"(Apr|Jun|Sep|Nov) (\\d\\d?), \\d{4}": {
"output": "\\2",
"next": {
"...=val <= 30": {
"algorithm": "dt",
"output": false
}
}
},
"Feb (\\d\\d?), (\\d{4})": {
"output": {
"day": "\\1",
"year": "\\2"
},
"next": {
"...=val.day <= 28": {
"algorithm": "dt",
"output": false
},
"...=val.day == 29 && (val.year % 4) == 0 && !((val.year % 100) == 0 && (val.year % 400) != 0)": {
"algorithm": "dt",
"output": true
}
}
}
}
| Input | Output |
|---|---|
| Jun 6, 2021 | false |
| Feb 29, 2000 | true |
| Feb 29, 2004 | true |
| Feb 29, 2001 | Feb 29, 2001 |
| Jun 32, 2021 | Jun 32, 2021 |