Skip to content

Commit fda3b25

Browse files
committed
Added formatters configuration file reader log2timeline#444
1 parent 81869dd commit fda3b25

File tree

5 files changed

+273
-2
lines changed

5 files changed

+273
-2
lines changed

plaso/engine/yaml_filter_file.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def _ReadFilterDefinition(self, filter_definition_values):
8989
paths=paths)
9090

9191
def _ReadFromFileObject(self, file_object):
92-
"""Reads the path filters from the YAML-based filter file-like object.
92+
"""Reads the path filters from a file-like object.
9393
9494
Args:
9595
file_object (file): filter file-like object.
+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# -*- coding: utf-8 -*-
2+
"""YAML-based formatters file."""
3+
4+
from __future__ import unicode_literals
5+
6+
import io
7+
import yaml
8+
9+
from plaso.formatters import interface
10+
from plaso.lib import errors
11+
12+
13+
class YAMLFormattersFile(object):
14+
"""YAML-based formatters file.
15+
16+
A YAML-based formatters file contains one or more event formatters.
17+
type: 'conditional'
18+
data_type: 'fs:stat'
19+
message:
20+
- '{display_name}'
21+
- 'Type: {file_entry_type}'
22+
- '({unallocated})'
23+
short_message:
24+
- '{filename}'
25+
short_source: 'FILE'
26+
source: 'File system'
27+
28+
Where:
29+
* type, defines the formatter data type, which can be "basic" or
30+
"conditional";
31+
* data_type, defines the corresponding event data type;
32+
* message, defines a list of message string pieces;
33+
* short_message, defines the short message string pieces;
34+
* short_source, defines the short source;
35+
* source, defines the source.
36+
"""
37+
38+
_SUPPORTED_KEYS = frozenset([
39+
'data_type',
40+
'message',
41+
'short_message',
42+
'short_source',
43+
'source',
44+
'type'])
45+
46+
def _ReadFormatterDefinition(self, formatter_definition_values):
47+
"""Reads an event formatter definition from a dictionary.
48+
49+
Args:
50+
formatter_definition_values (dict[str, object]): formatter definition
51+
values.
52+
53+
Returns:
54+
EventFormatter: an event formatter.
55+
56+
Raises:
57+
ParseError: if the format of the formatter definition is not set
58+
or incorrect.
59+
"""
60+
if not formatter_definition_values:
61+
raise errors.ParseError('Missing formatter definition values.')
62+
63+
different_keys = set(formatter_definition_values) - self._SUPPORTED_KEYS
64+
if different_keys:
65+
different_keys = ', '.join(different_keys)
66+
raise errors.ParseError('Undefined keys: {0:s}'.format(different_keys))
67+
68+
formatter_type = formatter_definition_values.get('type', None)
69+
if not formatter_type:
70+
raise errors.ParseError(
71+
'Invalid event formatter definition missing type.')
72+
73+
if formatter_type not in ('basic', 'conditional'):
74+
raise errors.ParseError(
75+
'Invalid event formatter definition unsupported type: {0!s}.'.format(
76+
formatter_type))
77+
78+
data_type = formatter_definition_values.get('data_type', None)
79+
if not data_type:
80+
raise errors.ParseError(
81+
'Invalid event formatter definition missing data type.')
82+
83+
message = formatter_definition_values.get('message', None)
84+
if not message:
85+
raise errors.ParseError(
86+
'Invalid event formatter definition missing message.')
87+
88+
short_message = formatter_definition_values.get('short_message', None)
89+
if not short_message:
90+
raise errors.ParseError(
91+
'Invalid event formatter definition missing short message.')
92+
93+
short_source = formatter_definition_values.get('short_source', None)
94+
if not short_source:
95+
raise errors.ParseError(
96+
'Invalid event formatter definition missing short source.')
97+
98+
source = formatter_definition_values.get('source', None)
99+
if not source:
100+
raise errors.ParseError(
101+
'Invalid event formatter definition missing source.')
102+
103+
if formatter_type == 'basic':
104+
formatter = interface.EventFormatter()
105+
# TODO: check if message and short_message are strings
106+
formatter.FORMAT_STRING = message
107+
formatter.FORMAT_STRING_SHORT = short_message
108+
109+
elif formatter_type == 'conditional':
110+
formatter = interface.ConditionalEventFormatter()
111+
# TODO: check if message and short_message are list of strings
112+
formatter.FORMAT_STRING_PIECES = message
113+
formatter.FORMAT_STRING_SHORT_PIECES = short_message
114+
115+
formatter.DATA_TYPE = data_type
116+
formatter.SOURCE_LONG = source
117+
formatter.SOURCE_SHORT = short_source
118+
119+
return formatter
120+
121+
def _ReadFromFileObject(self, file_object):
122+
"""Reads the event formatters from a file-like object.
123+
124+
Args:
125+
file_object (file): formatters file-like object.
126+
127+
Yields:
128+
EventFormatter: event formatters.
129+
"""
130+
yaml_generator = yaml.safe_load_all(file_object)
131+
132+
for yaml_definition in yaml_generator:
133+
yield self._ReadFormatterDefinition(yaml_definition)
134+
135+
def ReadFromFile(self, path):
136+
"""Reads the event formatters from the YAML-based formatters file.
137+
138+
Args:
139+
path (str): path to a formatters file.
140+
141+
Returns:
142+
list[EventFormatter]: event formatters.
143+
"""
144+
with io.open(path, 'r', encoding='utf-8') as file_object:
145+
return list(self._ReadFromFileObject(file_object))

test_data/formatters/format_test.yaml

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# YAML-based formatters file for testing format features.
2+
3+
type: 'conditional'
4+
data_type: 'fs:stat'
5+
message:
6+
- '{display_name}'
7+
- 'Type: {file_entry_type}'
8+
- '({unallocated})'
9+
short_message:
10+
- '{filename}'
11+
short_source: 'FILE'
12+
source: 'File system'

tests/formatters/init_imports.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class FormattersImportTest(test_lib.ImportCheckTestCase):
1616
_CLI_HELPERS_PATH = os.path.join(os.getcwd(), 'plaso', 'formatters')
1717
_IGNORABLE_FILES = frozenset([
1818
'default.py', 'interface.py', 'logger.py', 'manager.py', 'mediator.py',
19-
'winevt_rc.py'])
19+
'winevt_rc.py', 'yaml_formatters_file.py'])
2020

2121
def testFormattersImported(self):
2222
"""Tests that all parsers are imported."""
+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
"""Tests for the YAML-based formatters file."""
4+
5+
from __future__ import unicode_literals
6+
7+
import io
8+
import unittest
9+
10+
from plaso.formatters import yaml_formatters_file
11+
from plaso.lib import errors
12+
13+
from tests import test_lib as shared_test_lib
14+
15+
16+
class YAMLFormattersFileTest(shared_test_lib.BaseTestCase):
17+
"""Tests for the YAML-based formatters file."""
18+
19+
# pylint: disable=protected-access
20+
21+
def testReadFormatterDefinition(self):
22+
"""Tests the _ReadFormatterDefinition function."""
23+
test_formatters_file = yaml_formatters_file.YAMLFormattersFile()
24+
25+
formatter = test_formatters_file._ReadFormatterDefinition({
26+
'type': 'conditional',
27+
'data_type': 'fs:stat',
28+
'message': [
29+
'{display_name}',
30+
'Type: {file_entry_type}',
31+
'({unallocated})'],
32+
'short_message': [
33+
'{filename}'],
34+
'short_source': 'FILE',
35+
'source': 'File system'})
36+
37+
self.assertIsNotNone(formatter)
38+
self.assertEqual(formatter.DATA_TYPE, 'fs:stat')
39+
40+
with self.assertRaises(errors.ParseError):
41+
test_formatters_file._ReadFormatterDefinition({})
42+
43+
with self.assertRaises(errors.ParseError):
44+
test_formatters_file._ReadFormatterDefinition({'type': 'bogus'})
45+
46+
with self.assertRaises(errors.ParseError):
47+
test_formatters_file._ReadFormatterDefinition({'type': 'conditional'})
48+
49+
with self.assertRaises(errors.ParseError):
50+
test_formatters_file._ReadFormatterDefinition({
51+
'type': 'conditional',
52+
'data_type': 'fs:stat'})
53+
54+
with self.assertRaises(errors.ParseError):
55+
test_formatters_file._ReadFormatterDefinition({
56+
'type': 'conditional',
57+
'data_type': 'fs:stat',
58+
'message': [
59+
'{display_name}',
60+
'Type: {file_entry_type}',
61+
'({unallocated})']})
62+
63+
with self.assertRaises(errors.ParseError):
64+
test_formatters_file._ReadFormatterDefinition({
65+
'type': 'conditional',
66+
'data_type': 'fs:stat',
67+
'message': [
68+
'{display_name}',
69+
'Type: {file_entry_type}',
70+
'({unallocated})'],
71+
'short_message': [
72+
'{filename}']})
73+
74+
with self.assertRaises(errors.ParseError):
75+
test_formatters_file._ReadFormatterDefinition({
76+
'type': 'conditional',
77+
'data_type': 'fs:stat',
78+
'message': [
79+
'{display_name}',
80+
'Type: {file_entry_type}',
81+
'({unallocated})'],
82+
'short_message': [
83+
'{filename}'],
84+
'short_source': 'FILE'})
85+
86+
with self.assertRaises(errors.ParseError):
87+
test_formatters_file._ReadFormatterDefinition({'bogus': 'error'})
88+
89+
@shared_test_lib.skipUnlessHasTestFile(['formatters', 'format_test.yaml'])
90+
def testReadFromFileObject(self):
91+
"""Tests the _ReadFromFileObject function."""
92+
test_formatters_file = yaml_formatters_file.YAMLFormattersFile()
93+
94+
test_path = self._GetTestFilePath(['formatters', 'format_test.yaml'])
95+
with io.open(test_path, 'r', encoding='utf-8') as file_object:
96+
formatters = list(test_formatters_file._ReadFromFileObject(file_object))
97+
98+
self.assertEqual(len(formatters), 1)
99+
100+
@shared_test_lib.skipUnlessHasTestFile(['formatters', 'format_test.yaml'])
101+
def testReadFromFile(self):
102+
"""Tests the ReadFromFile function."""
103+
test_formatters_file = yaml_formatters_file.YAMLFormattersFile()
104+
105+
test_path = self._GetTestFilePath(['formatters', 'format_test.yaml'])
106+
formatters = test_formatters_file.ReadFromFile(test_path)
107+
108+
self.assertEqual(len(formatters), 1)
109+
110+
self.assertEqual(formatters[0].DATA_TYPE, 'fs:stat')
111+
112+
113+
if __name__ == '__main__':
114+
unittest.main()

0 commit comments

Comments
 (0)