flow.record 3.13.dev4__tar.gz → 3.13.dev6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {flow.record-3.13.dev4/flow.record.egg-info → flow.record-3.13.dev6}/PKG-INFO +1 -1
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/__init__.py +2 -2
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/avro.py +2 -2
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/jsonfile.py +2 -2
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/line.py +2 -2
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/stream.py +3 -3
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/text.py +2 -2
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/xlsx.py +2 -2
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/base.py +44 -37
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/selector.py +7 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/version.py +2 -2
- {flow.record-3.13.dev4 → flow.record-3.13.dev6/flow.record.egg-info}/PKG-INFO +1 -1
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_record_adapter.py +33 -8
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_selector.py +24 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/COPYRIGHT +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/LICENSE +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/MANIFEST.in +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/README.md +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/examples/filesystem.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/examples/passivedns.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/examples/records.json +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/examples/tcpconn.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/__init__.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/archive.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/broker.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/csvfile.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/elastic.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/mongo.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/split.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/splunk.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/exceptions.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/fieldtypes/__init__.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/fieldtypes/credential.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/fieldtypes/net/__init__.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/fieldtypes/net/ip.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/fieldtypes/net/ipv4.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/fieldtypes/net/tcp.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/fieldtypes/net/udp.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/jsonpacker.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/packer.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/stream.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/tools/__init__.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/tools/geoip.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/tools/rdump.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/utils.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/whitelist.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow.record.egg-info/SOURCES.txt +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow.record.egg-info/dependency_links.txt +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow.record.egg-info/entry_points.txt +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow.record.egg-info/requires.txt +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow.record.egg-info/top_level.txt +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/pyproject.toml +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/setup.cfg +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/__init__.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/_utils.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/docs/Makefile +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/docs/conf.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/docs/index.rst +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/selector_explain_example.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/standalone_test.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_avro.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_avro_adapter.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_compiled_selector.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_deprecations.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_fieldtype_ip.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_fieldtypes.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_json_packer.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_json_record_adapter.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_multi_timestamp.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_packer.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_rdump.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_record.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_record_descriptor.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_regression.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_splunk_adapter.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/utils_inspect.py +0 -0
- {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: flow.record
|
|
3
|
-
Version: 3.13.
|
|
3
|
+
Version: 3.13.dev6
|
|
4
4
|
Summary: A library for defining and creating structured data (called records) that can be streamed to disk or piped to other tools that use flow.record
|
|
5
5
|
Author-email: Dissect Team <dissect@fox-it.com>
|
|
6
6
|
License: Affero General Public License v3
|
|
@@ -17,8 +17,8 @@ from flow.record.base import (
|
|
|
17
17
|
dynamic_fieldtype,
|
|
18
18
|
extend_record,
|
|
19
19
|
iter_timestamped_records,
|
|
20
|
-
open_file,
|
|
21
20
|
open_path,
|
|
21
|
+
open_path_or_stream,
|
|
22
22
|
open_stream,
|
|
23
23
|
stream,
|
|
24
24
|
)
|
|
@@ -51,7 +51,7 @@ __all__ = [
|
|
|
51
51
|
"JsonRecordPacker",
|
|
52
52
|
"RecordStreamWriter",
|
|
53
53
|
"RecordStreamReader",
|
|
54
|
-
"
|
|
54
|
+
"open_path_or_stream",
|
|
55
55
|
"open_path",
|
|
56
56
|
"open_stream",
|
|
57
57
|
"stream",
|
|
@@ -53,7 +53,7 @@ class AvroWriter(AbstractWriter):
|
|
|
53
53
|
writer = None
|
|
54
54
|
|
|
55
55
|
def __init__(self, path, key=None, **kwargs):
|
|
56
|
-
self.fp = record.
|
|
56
|
+
self.fp = record.open_path_or_stream(path, "wb")
|
|
57
57
|
|
|
58
58
|
self.desc = None
|
|
59
59
|
self.schema = None
|
|
@@ -93,7 +93,7 @@ class AvroReader(AbstractReader):
|
|
|
93
93
|
fp = None
|
|
94
94
|
|
|
95
95
|
def __init__(self, path, selector=None, **kwargs):
|
|
96
|
-
self.fp = record.
|
|
96
|
+
self.fp = record.open_path_or_stream(path, "rb")
|
|
97
97
|
self.selector = make_selector(selector)
|
|
98
98
|
|
|
99
99
|
self.reader = fastavro.reader(self.fp)
|
|
@@ -23,7 +23,7 @@ class JsonfileWriter(AbstractWriter):
|
|
|
23
23
|
|
|
24
24
|
def __init__(self, path, indent=None, descriptors=True, **kwargs):
|
|
25
25
|
self.descriptors = str(descriptors).lower() in ("true", "1")
|
|
26
|
-
self.fp = record.
|
|
26
|
+
self.fp = record.open_path_or_stream(path, "w")
|
|
27
27
|
if isinstance(indent, str):
|
|
28
28
|
indent = int(indent)
|
|
29
29
|
self.packer = JsonRecordPacker(indent=indent, pack_descriptors=self.descriptors)
|
|
@@ -55,7 +55,7 @@ class JsonfileReader(AbstractReader):
|
|
|
55
55
|
|
|
56
56
|
def __init__(self, path, selector=None, **kwargs):
|
|
57
57
|
self.selector = make_selector(selector)
|
|
58
|
-
self.fp = record.
|
|
58
|
+
self.fp = record.open_path_or_stream(path, "r")
|
|
59
59
|
self.packer = JsonRecordPacker()
|
|
60
60
|
|
|
61
61
|
def close(self):
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from flow.record import
|
|
1
|
+
from flow.record import open_path_or_stream
|
|
2
2
|
from flow.record.adapter import AbstractWriter
|
|
3
3
|
from flow.record.utils import is_stdout
|
|
4
4
|
|
|
@@ -16,7 +16,7 @@ class LineWriter(AbstractWriter):
|
|
|
16
16
|
fp = None
|
|
17
17
|
|
|
18
18
|
def __init__(self, path, fields=None, exclude=None, **kwargs):
|
|
19
|
-
self.fp =
|
|
19
|
+
self.fp = open_path_or_stream(path, "wb")
|
|
20
20
|
self.count = 0
|
|
21
21
|
self.fields = fields
|
|
22
22
|
self.exclude = exclude
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from typing import Iterator, Union
|
|
2
2
|
|
|
3
|
-
from flow.record import Record, RecordOutput, RecordStreamReader,
|
|
3
|
+
from flow.record import Record, RecordOutput, RecordStreamReader, open_path_or_stream
|
|
4
4
|
from flow.record.adapter import AbstractReader, AbstractWriter
|
|
5
5
|
from flow.record.selector import Selector
|
|
6
6
|
from flow.record.utils import is_stdout
|
|
@@ -19,7 +19,7 @@ class StreamWriter(AbstractWriter):
|
|
|
19
19
|
stream = None
|
|
20
20
|
|
|
21
21
|
def __init__(self, path: str, clobber=True, **kwargs):
|
|
22
|
-
self.fp =
|
|
22
|
+
self.fp = open_path_or_stream(path, "wb", clobber=clobber)
|
|
23
23
|
self.stream = RecordOutput(self.fp)
|
|
24
24
|
|
|
25
25
|
def write(self, record: Record) -> None:
|
|
@@ -46,7 +46,7 @@ class StreamReader(AbstractReader):
|
|
|
46
46
|
stream = None
|
|
47
47
|
|
|
48
48
|
def __init__(self, path: str, selector: Union[str, Selector] = None, **kwargs):
|
|
49
|
-
self.fp =
|
|
49
|
+
self.fp = open_path_or_stream(path, "rb")
|
|
50
50
|
self.stream = RecordStreamReader(self.fp, selector=selector)
|
|
51
51
|
|
|
52
52
|
def __iter__(self) -> Iterator[Record]:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from flow.record import
|
|
1
|
+
from flow.record import open_path_or_stream
|
|
2
2
|
from flow.record.adapter import AbstractWriter
|
|
3
3
|
from flow.record.utils import is_stdout
|
|
4
4
|
|
|
@@ -27,7 +27,7 @@ class TextWriter(AbstractWriter):
|
|
|
27
27
|
fp = None
|
|
28
28
|
|
|
29
29
|
def __init__(self, path, flush=True, format_spec=None, **kwargs):
|
|
30
|
-
self.fp =
|
|
30
|
+
self.fp = open_path_or_stream(path, "wb")
|
|
31
31
|
self.auto_flush = flush
|
|
32
32
|
self.format_spec = format_spec
|
|
33
33
|
|
|
@@ -19,7 +19,7 @@ class XlsxWriter(AbstractWriter):
|
|
|
19
19
|
wb = None
|
|
20
20
|
|
|
21
21
|
def __init__(self, path, **kwargs):
|
|
22
|
-
self.fp = record.
|
|
22
|
+
self.fp = record.open_path_or_stream(path, "wb")
|
|
23
23
|
self.wb = openpyxl.Workbook()
|
|
24
24
|
self.ws = self.wb.active
|
|
25
25
|
self.desc = None
|
|
@@ -51,7 +51,7 @@ class XlsxReader(AbstractReader):
|
|
|
51
51
|
|
|
52
52
|
def __init__(self, path, selector=None, **kwargs):
|
|
53
53
|
self.selector = make_selector(selector)
|
|
54
|
-
self.fp = record.
|
|
54
|
+
self.fp = record.open_path_or_stream(path, "rb")
|
|
55
55
|
self.desc = None
|
|
56
56
|
self.wb = openpyxl.load_workbook(self.fp)
|
|
57
57
|
self.ws = self.wb.active
|
|
@@ -645,6 +645,8 @@ def DynamicDescriptor(name, fields):
|
|
|
645
645
|
|
|
646
646
|
|
|
647
647
|
def open_stream(fp: BinaryIO, mode: str) -> BinaryIO:
|
|
648
|
+
if "w" in mode:
|
|
649
|
+
return fp
|
|
648
650
|
if not hasattr(fp, "peek"):
|
|
649
651
|
fp = io.BufferedReader(fp)
|
|
650
652
|
|
|
@@ -682,13 +684,13 @@ def find_adapter_for_stream(fp: BinaryIO) -> tuple[BinaryIO, Optional[str]]:
|
|
|
682
684
|
return fp, None
|
|
683
685
|
|
|
684
686
|
|
|
685
|
-
def
|
|
687
|
+
def open_path_or_stream(path: Union[str, Path, BinaryIO], mode: str, clobber: bool = True) -> IO:
|
|
686
688
|
if isinstance(path, Path):
|
|
687
689
|
path = str(path)
|
|
688
690
|
if isinstance(path, str):
|
|
689
691
|
return open_path(path, mode, clobber)
|
|
690
692
|
elif isinstance(path, io.IOBase):
|
|
691
|
-
return open_stream(path,
|
|
693
|
+
return open_stream(path, mode)
|
|
692
694
|
else:
|
|
693
695
|
raise ValueError(f"Unsupported path type {path}")
|
|
694
696
|
|
|
@@ -798,41 +800,45 @@ def RecordAdapter(
|
|
|
798
800
|
cls_url = p.netloc + p.path
|
|
799
801
|
if sub_adapter:
|
|
800
802
|
cls_url = sub_adapter + "://" + cls_url
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
arg_dict = kwargs.copy()
|
|
817
|
-
|
|
818
|
-
# If a user did not provide a url, we have to peek into the stream to be able to determine the right adapter
|
|
819
|
-
# based on magic bytes encountered in the first few bytes of the stream.
|
|
820
|
-
if adapter is None:
|
|
821
|
-
cls_stream, adapter = find_adapter_for_stream(cls_stream)
|
|
803
|
+
if out is False:
|
|
804
|
+
if url in ("-", ""):
|
|
805
|
+
# For reading stdin, we cannot rely on an extension to know what sort of stream is incoming. Thus, we will
|
|
806
|
+
# treat it as a 'fileobj', where we can peek into the stream and try to select the appropriate adapter.
|
|
807
|
+
fileobj = getattr(sys.stdin, "buffer", sys.stdin)
|
|
808
|
+
if fileobj is not None:
|
|
809
|
+
# This record adapter has received a file-like object for record reading
|
|
810
|
+
# We just need to find the right adapter by peeking into the first few bytes.
|
|
811
|
+
|
|
812
|
+
# First, we open the stream. If the stream is compressed, open_stream will wrap it for us into a
|
|
813
|
+
# decompressor.
|
|
814
|
+
cls_stream = open_stream(fileobj, "rb")
|
|
815
|
+
|
|
816
|
+
# If a user did not provide a url, we have to peek into the stream to be able to determine the right adapter
|
|
817
|
+
# based on magic bytes encountered in the first few bytes of the stream.
|
|
822
818
|
if adapter is None:
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
819
|
+
# If we could not infere an adapter from the url, we have a stream that will be transparently
|
|
820
|
+
# decompressed but we still do not know what adapter to use. This requires a new peek into the
|
|
821
|
+
# transparent stream. This peek will cause the stream pointer to be moved. Therefore,
|
|
822
|
+
# find_adapter_for_stream returns both a BinaryIO-supportive object that can correctly read the adjusted
|
|
823
|
+
# stream, and a string indicating the type of adapter to be used on said stream.
|
|
824
|
+
cls_stream, adapter = find_adapter_for_stream(cls_stream)
|
|
825
|
+
if adapter is None:
|
|
826
|
+
# As peek() can result in a larger buffer than requested, so we truncate it just to be sure
|
|
827
|
+
peek_data = cls_stream.peek(RECORDSTREAM_MAGIC_DEPTH)[:RECORDSTREAM_MAGIC_DEPTH]
|
|
828
|
+
if peek_data and peek_data.startswith(b"<"):
|
|
829
|
+
raise RecordAdapterNotFound(
|
|
830
|
+
(
|
|
831
|
+
f"Could not find a reader for input {peek_data!r}. Are you perhaps "
|
|
832
|
+
"entering record text, rather than a record stream? This can be fixed by using "
|
|
833
|
+
"'rdump -w -' to write a record stream to stdout."
|
|
834
|
+
)
|
|
833
835
|
)
|
|
834
|
-
)
|
|
835
|
-
|
|
836
|
+
raise RecordAdapterNotFound("Could not find adapter for file-like object")
|
|
837
|
+
|
|
838
|
+
# Now that we found an adapter, we will fall back into the same code path as when a URL is given. As the url
|
|
839
|
+
# parsing path copied kwargs into an arg_dict variable, we will do the same so we do not get a variable
|
|
840
|
+
# referenced before assignment error.
|
|
841
|
+
arg_dict = kwargs.copy()
|
|
836
842
|
|
|
837
843
|
# Now that we know which adapter is needed, we import it.
|
|
838
844
|
mod = importlib.import_module("flow.record.adapter.{}".format(adapter))
|
|
@@ -845,9 +851,10 @@ def RecordAdapter(
|
|
|
845
851
|
if out:
|
|
846
852
|
arg_dict["clobber"] = clobber
|
|
847
853
|
log.debug("Creating {!r} for {!r} with args {!r}".format(cls, url, arg_dict))
|
|
848
|
-
|
|
849
|
-
if fileobj is not None:
|
|
854
|
+
if cls_stream is not None:
|
|
850
855
|
return cls(cls_stream, **arg_dict)
|
|
856
|
+
if fileobj is not None:
|
|
857
|
+
return cls(fileobj, **arg_dict)
|
|
851
858
|
return cls(cls_url, **arg_dict)
|
|
852
859
|
|
|
853
860
|
|
|
@@ -355,6 +355,12 @@ class WrappedRecord:
|
|
|
355
355
|
def __getattr__(self, k):
|
|
356
356
|
return getattr(self.record, k, NONE_OBJECT)
|
|
357
357
|
|
|
358
|
+
def __str__(self) -> str:
|
|
359
|
+
return str(self.record)
|
|
360
|
+
|
|
361
|
+
def __repr__(self) -> str:
|
|
362
|
+
return repr(self.record)
|
|
363
|
+
|
|
358
364
|
|
|
359
365
|
class CompiledSelector:
|
|
360
366
|
"""CompiledSelector is faster than Selector but unsafe if you don't trust the query."""
|
|
@@ -546,6 +552,7 @@ class RecordContextMatcher:
|
|
|
546
552
|
"True": True,
|
|
547
553
|
"False": False,
|
|
548
554
|
"str": str,
|
|
555
|
+
"repr": repr,
|
|
549
556
|
"fields": rec._desc.getfields,
|
|
550
557
|
"any": any,
|
|
551
558
|
"all": all,
|
|
@@ -12,5 +12,5 @@ __version__: str
|
|
|
12
12
|
__version_tuple__: VERSION_TUPLE
|
|
13
13
|
version_tuple: VERSION_TUPLE
|
|
14
14
|
|
|
15
|
-
__version__ = version = '3.13.
|
|
16
|
-
__version_tuple__ = version_tuple = (3, 13, '
|
|
15
|
+
__version__ = version = '3.13.dev6'
|
|
16
|
+
__version_tuple__ = version_tuple = (3, 13, 'dev6')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: flow.record
|
|
3
|
-
Version: 3.13.
|
|
3
|
+
Version: 3.13.dev6
|
|
4
4
|
Summary: A library for defining and creating structured data (called records) that can be streamed to disk or piped to other tools that use flow.record
|
|
5
5
|
Author-email: Dissect Team <dissect@fox-it.com>
|
|
6
6
|
License: Affero General Public License v3
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import platform
|
|
3
3
|
import sys
|
|
4
|
+
from io import BytesIO
|
|
4
5
|
|
|
5
6
|
import pytest
|
|
6
7
|
|
|
7
|
-
try:
|
|
8
|
-
from StringIO import StringIO
|
|
9
|
-
except ImportError:
|
|
10
|
-
from io import BytesIO as StringIO
|
|
11
|
-
|
|
12
8
|
from flow.record import (
|
|
13
9
|
PathTemplateWriter,
|
|
10
|
+
RecordAdapter,
|
|
14
11
|
RecordArchiver,
|
|
15
12
|
RecordDescriptor,
|
|
16
13
|
RecordOutput,
|
|
@@ -18,7 +15,7 @@ from flow.record import (
|
|
|
18
15
|
RecordStreamReader,
|
|
19
16
|
RecordWriter,
|
|
20
17
|
)
|
|
21
|
-
from flow.record.adapter.stream import StreamReader
|
|
18
|
+
from flow.record.adapter.stream import StreamReader, StreamWriter
|
|
22
19
|
from flow.record.base import (
|
|
23
20
|
BZ2_MAGIC,
|
|
24
21
|
GZIP_MAGIC,
|
|
@@ -33,7 +30,7 @@ from ._utils import generate_records
|
|
|
33
30
|
|
|
34
31
|
|
|
35
32
|
def test_stream_writer_reader():
|
|
36
|
-
fp =
|
|
33
|
+
fp = BytesIO()
|
|
37
34
|
out = RecordOutput(fp)
|
|
38
35
|
for rec in generate_records():
|
|
39
36
|
out.write(rec)
|
|
@@ -48,7 +45,7 @@ def test_stream_writer_reader():
|
|
|
48
45
|
|
|
49
46
|
|
|
50
47
|
def test_recordstream_filelike_object():
|
|
51
|
-
fp =
|
|
48
|
+
fp = BytesIO()
|
|
52
49
|
out = RecordOutput(fp)
|
|
53
50
|
for rec in generate_records():
|
|
54
51
|
out.write(rec)
|
|
@@ -477,3 +474,31 @@ def test_csvfilereader(tmp_path):
|
|
|
477
474
|
with RecordReader(f"csvfile://{path}", selector="r.count == '2'") as reader:
|
|
478
475
|
for i, rec in enumerate(reader):
|
|
479
476
|
assert rec.count == "2"
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def test_file_like_writer_reader() -> None:
|
|
480
|
+
test_buf = BytesIO()
|
|
481
|
+
|
|
482
|
+
adapter = RecordAdapter(fileobj=test_buf, out=True)
|
|
483
|
+
|
|
484
|
+
assert isinstance(adapter, StreamWriter)
|
|
485
|
+
|
|
486
|
+
# Add mock records
|
|
487
|
+
test_records = list(generate_records(10))
|
|
488
|
+
for record in test_records:
|
|
489
|
+
adapter.write(record)
|
|
490
|
+
|
|
491
|
+
adapter.flush()
|
|
492
|
+
|
|
493
|
+
# Grab the bytes before closing the BytesIO object.
|
|
494
|
+
read_buf = BytesIO(test_buf.getvalue())
|
|
495
|
+
|
|
496
|
+
# Close the writer and assure the object has been closed
|
|
497
|
+
adapter.close()
|
|
498
|
+
|
|
499
|
+
# Verify if the written record stream is something we can read
|
|
500
|
+
reader = RecordAdapter(fileobj=read_buf)
|
|
501
|
+
read_records = list(reader)
|
|
502
|
+
assert len(read_records) == 10
|
|
503
|
+
for idx, record in enumerate(read_records):
|
|
504
|
+
assert record == test_records[idx]
|
|
@@ -53,6 +53,30 @@ def test_selector():
|
|
|
53
53
|
assert TestRecord() in Selector("invalid_func(r.invalid_field, 1337) or r.id == 4")
|
|
54
54
|
|
|
55
55
|
|
|
56
|
+
def test_selector_str_repr():
|
|
57
|
+
TestRecord = RecordDescriptor(
|
|
58
|
+
"test/record",
|
|
59
|
+
[
|
|
60
|
+
("string", "query"),
|
|
61
|
+
("string", "url"),
|
|
62
|
+
],
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
assert TestRecord("foo", "bar") in Selector("'foo' in str(r)")
|
|
66
|
+
assert TestRecord("foo", "bar") in Selector("'test/record' in str(r)")
|
|
67
|
+
assert TestRecord("foo", "bar") in Selector("'foo' in repr(r)")
|
|
68
|
+
assert TestRecord("foo", "bar") in Selector("'test/record' in repr(r)")
|
|
69
|
+
assert TestRecord("foo", "bar") in CompiledSelector("'foo' in str(r)")
|
|
70
|
+
assert TestRecord("foo", "bar") in CompiledSelector("'test/record' in str(r)")
|
|
71
|
+
assert TestRecord("foo", "bar") in CompiledSelector("'foo' in repr(r)")
|
|
72
|
+
assert TestRecord("foo", "bar") in CompiledSelector("'test/record' in repr(r)")
|
|
73
|
+
|
|
74
|
+
assert TestRecord("foo", "bar") not in Selector("'nope' in str(r)")
|
|
75
|
+
assert TestRecord("foo", "bar") not in Selector("'nope' in repr(r)")
|
|
76
|
+
assert TestRecord("foo", "bar") not in CompiledSelector("'nope' in str(r)")
|
|
77
|
+
assert TestRecord("foo", "bar") not in CompiledSelector("'nope' in repr(r)")
|
|
78
|
+
|
|
79
|
+
|
|
56
80
|
def test_selector_meta_query_true():
|
|
57
81
|
source = "internal/flow.record.test"
|
|
58
82
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|