flow.record 3.22.dev2__py3-none-any.whl → 3.22.dev4__py3-none-any.whl
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/base.py +2 -0
- flow/record/fieldtypes/__init__.py +81 -72
- flow/record/jsonpacker.py +1 -4
- flow/record/stream.py +7 -2
- flow/record/version.py +2 -2
- {flow_record-3.22.dev2.dist-info → flow_record-3.22.dev4.dist-info}/METADATA +1 -1
- {flow_record-3.22.dev2.dist-info → flow_record-3.22.dev4.dist-info}/RECORD +12 -12
- {flow_record-3.22.dev2.dist-info → flow_record-3.22.dev4.dist-info}/WHEEL +0 -0
- {flow_record-3.22.dev2.dist-info → flow_record-3.22.dev4.dist-info}/entry_points.txt +0 -0
- {flow_record-3.22.dev2.dist-info → flow_record-3.22.dev4.dist-info}/licenses/COPYRIGHT +0 -0
- {flow_record-3.22.dev2.dist-info → flow_record-3.22.dev4.dist-info}/licenses/LICENSE +0 -0
- {flow_record-3.22.dev2.dist-info → flow_record-3.22.dev4.dist-info}/top_level.txt +0 -0
flow/record/base.py
CHANGED
|
@@ -893,6 +893,8 @@ def RecordAdapter(
|
|
|
893
893
|
"entering record text, rather than a record stream? This can be fixed by using "
|
|
894
894
|
"'rdump -w -' to write a record stream to stdout."
|
|
895
895
|
)
|
|
896
|
+
if not peek_data:
|
|
897
|
+
raise EOFError("Empty input stream")
|
|
896
898
|
raise RecordAdapterNotFound("Could not find adapter for file-like object")
|
|
897
899
|
|
|
898
900
|
# Now that we found an adapter, we will fall back into the same code path as when a URL is given. As the url
|
|
@@ -752,41 +752,39 @@ class windows_path(pathlib.PureWindowsPath, path):
|
|
|
752
752
|
|
|
753
753
|
|
|
754
754
|
class command(FieldType):
|
|
755
|
-
|
|
756
|
-
args: list[str] | None = None
|
|
755
|
+
"""The command fieldtype splits a command string into an ``executable`` and its arguments.
|
|
757
756
|
|
|
758
|
-
|
|
759
|
-
|
|
757
|
+
Args:
|
|
758
|
+
value: the string that contains the command and arguments
|
|
759
|
+
path_type: When specified it forces the command to use a specific path type
|
|
760
760
|
|
|
761
|
-
|
|
762
|
-
if cls is not command:
|
|
763
|
-
return super().__new__(cls)
|
|
761
|
+
Example:
|
|
764
762
|
|
|
765
|
-
|
|
766
|
-
|
|
763
|
+
.. code-block:: text
|
|
764
|
+
|
|
765
|
+
'c:\\windows\\malware.exe /info' -> windows_path('c:\\windows\\malware.exe) ['/info']
|
|
766
|
+
'/usr/bin/env bash' -> posix_path('/usr/bin/env') ['bash']
|
|
767
767
|
|
|
768
|
-
#
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
# the strip and check for ":" on the second line is for `<drive_letter>:`
|
|
773
|
-
stripped_value = value.lstrip("\"'")
|
|
774
|
-
windows = value.startswith((r"\\", "%")) or (len(stripped_value) >= 2 and stripped_value[1] == ":")
|
|
768
|
+
# In this situation, the executable path needs to be quoted.
|
|
769
|
+
'c:\\user\\John Doe\\malware.exe /all /the /things' -> windows_path('c:\\user\\John')
|
|
770
|
+
['Doe\\malware.exe /all /the /things']
|
|
771
|
+
"""
|
|
775
772
|
|
|
776
|
-
|
|
777
|
-
|
|
773
|
+
__executable: path
|
|
774
|
+
__args: tuple[str, ...]
|
|
778
775
|
|
|
779
|
-
|
|
780
|
-
if value is None:
|
|
781
|
-
return
|
|
776
|
+
__path_type: type[path]
|
|
782
777
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
778
|
+
def __init__(self, value: str = "", *, path_type: type[path] | None = None):
|
|
779
|
+
if not isinstance(value, str):
|
|
780
|
+
raise TypeError(f"Expected a value of type 'str' not {type(value)}")
|
|
781
|
+
|
|
782
|
+
raw = value.strip()
|
|
786
783
|
|
|
787
|
-
|
|
788
|
-
self.
|
|
789
|
-
|
|
784
|
+
# Detect the kind of path from value if not specified
|
|
785
|
+
self.__path_type = path_type or type(path(raw.lstrip("\"'")))
|
|
786
|
+
|
|
787
|
+
self.executable, self.args = self._split(raw)
|
|
790
788
|
|
|
791
789
|
def __repr__(self) -> str:
|
|
792
790
|
return f"(executable={self.executable!r}, args={self.args})"
|
|
@@ -795,66 +793,77 @@ class command(FieldType):
|
|
|
795
793
|
if isinstance(other, command):
|
|
796
794
|
return self.executable == other.executable and self.args == other.args
|
|
797
795
|
if isinstance(other, str):
|
|
798
|
-
return self.
|
|
796
|
+
return self.raw == other
|
|
799
797
|
if isinstance(other, (tuple, list)):
|
|
800
|
-
return self.executable == other[0] and self.args ==
|
|
798
|
+
return self.executable == other[0] and self.args == (*other[1:],)
|
|
801
799
|
|
|
802
800
|
return False
|
|
803
801
|
|
|
804
|
-
def _split(self, value: str) -> tuple[str,
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
return self._path_type(executable), args
|
|
802
|
+
def _split(self, value: str) -> tuple[str, tuple[str, ...]]:
|
|
803
|
+
if not value:
|
|
804
|
+
return "", ()
|
|
809
805
|
|
|
810
|
-
|
|
811
|
-
return
|
|
806
|
+
executable, *args = shlex.split(value, posix=self.__path_type is posix_path)
|
|
807
|
+
return executable.strip("'\" "), (*args,)
|
|
812
808
|
|
|
813
|
-
def _pack(self) -> tuple[
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
_exec, _ = self.executable._pack()
|
|
817
|
-
return ((_exec, self.args), command_type)
|
|
818
|
-
return (None, command_type)
|
|
819
|
-
|
|
820
|
-
@classmethod
|
|
821
|
-
def _unpack(cls, data: tuple[tuple[str, tuple] | None, int]) -> command:
|
|
822
|
-
_value, _type = data
|
|
823
|
-
if _type == TYPE_WINDOWS:
|
|
824
|
-
return windows_command(_value)
|
|
825
|
-
|
|
826
|
-
return posix_command(_value)
|
|
809
|
+
def _pack(self) -> tuple[str, int]:
|
|
810
|
+
path_type = TYPE_WINDOWS if self.__path_type is windows_path else TYPE_POSIX
|
|
811
|
+
return self.raw, path_type
|
|
827
812
|
|
|
828
813
|
@classmethod
|
|
829
|
-
def
|
|
830
|
-
|
|
814
|
+
def _unpack(cls, data: tuple[str, int]) -> command:
|
|
815
|
+
raw_str, path_type = data
|
|
816
|
+
if path_type == TYPE_POSIX:
|
|
817
|
+
return command(raw_str, path_type=posix_path)
|
|
818
|
+
if path_type == TYPE_WINDOWS:
|
|
819
|
+
return command(raw_str, path_type=windows_path)
|
|
820
|
+
# default, infer type of path from str
|
|
821
|
+
return command(raw_str)
|
|
831
822
|
|
|
832
|
-
@
|
|
833
|
-
def
|
|
834
|
-
return
|
|
823
|
+
@property
|
|
824
|
+
def executable(self) -> path:
|
|
825
|
+
return self.__executable
|
|
835
826
|
|
|
827
|
+
@property
|
|
828
|
+
def args(self) -> tuple[str, ...]:
|
|
829
|
+
return self.__args
|
|
836
830
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
831
|
+
@executable.setter
|
|
832
|
+
def executable(self, val: str | path | None) -> None:
|
|
833
|
+
self.__executable = self.__path_type(val)
|
|
840
834
|
|
|
835
|
+
@args.setter
|
|
836
|
+
def args(self, val: str | tuple[str, ...] | list[str] | None) -> None:
|
|
837
|
+
if val is None:
|
|
838
|
+
self.__args = ()
|
|
839
|
+
return
|
|
841
840
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
841
|
+
if isinstance(val, str):
|
|
842
|
+
self.__args = tuple(shlex.split(val, posix=self.__path_type is posix_path))
|
|
843
|
+
elif isinstance(val, list):
|
|
844
|
+
self.__args = tuple(val)
|
|
845
|
+
else:
|
|
846
|
+
self.__args = val
|
|
845
847
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
args = [" ".join(args)]
|
|
848
|
+
@property
|
|
849
|
+
def raw(self) -> str:
|
|
850
|
+
exe = str(self.executable)
|
|
850
851
|
|
|
851
|
-
|
|
852
|
+
if " " in exe:
|
|
853
|
+
exe = shlex.quote(exe)
|
|
852
854
|
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
855
|
+
result = [exe]
|
|
856
|
+
# Only quote on posix paths as shlex doesn't remove the quotes on non posix paths
|
|
857
|
+
if self.__path_type is posix_path:
|
|
858
|
+
result.extend(shlex.quote(part) if " " in part else part for part in self.args)
|
|
859
|
+
else:
|
|
860
|
+
result.extend(self.args)
|
|
861
|
+
return " ".join(result)
|
|
856
862
|
|
|
857
|
-
|
|
858
|
-
|
|
863
|
+
@classmethod
|
|
864
|
+
def from_posix(cls, value: str) -> command:
|
|
865
|
+
return command(value, path_type=posix_path)
|
|
859
866
|
|
|
860
|
-
|
|
867
|
+
@classmethod
|
|
868
|
+
def from_windows(cls, value: str) -> command:
|
|
869
|
+
return command(value, path_type=windows_path)
|
flow/record/jsonpacker.py
CHANGED
|
@@ -75,10 +75,7 @@ class JsonRecordPacker:
|
|
|
75
75
|
if isinstance(obj, fieldtypes.path):
|
|
76
76
|
return str(obj)
|
|
77
77
|
if isinstance(obj, fieldtypes.command):
|
|
78
|
-
return
|
|
79
|
-
"executable": obj.executable,
|
|
80
|
-
"args": obj.args,
|
|
81
|
-
}
|
|
78
|
+
return obj.raw
|
|
82
79
|
|
|
83
80
|
raise TypeError(f"Unpackable type {type(obj)}")
|
|
84
81
|
|
flow/record/stream.py
CHANGED
|
@@ -164,11 +164,13 @@ def record_stream(sources: list[str], selector: str | None = None) -> Iterator[R
|
|
|
164
164
|
print("[reading from stdin]", file=sys.stderr)
|
|
165
165
|
|
|
166
166
|
# Initial value for reader, in case of exception message
|
|
167
|
-
reader = "RecordReader"
|
|
167
|
+
reader: str | AbstractReader = "RecordReader"
|
|
168
168
|
try:
|
|
169
169
|
reader = RecordReader(src, selector=selector)
|
|
170
170
|
yield from reader
|
|
171
|
-
|
|
171
|
+
except EOFError as e:
|
|
172
|
+
# End of file reached, likely no records in source
|
|
173
|
+
log.warning("%s(%r): %s", reader, src, e)
|
|
172
174
|
except IOError as e:
|
|
173
175
|
if len(sources) == 1:
|
|
174
176
|
raise
|
|
@@ -184,6 +186,9 @@ def record_stream(sources: list[str], selector: str | None = None) -> Iterator[R
|
|
|
184
186
|
else:
|
|
185
187
|
log.warning("Exception in %r for %r: %s -- skipping to next reader", reader, src, aRepr.repr(e))
|
|
186
188
|
continue
|
|
189
|
+
finally:
|
|
190
|
+
if isinstance(reader, AbstractReader):
|
|
191
|
+
reader.close()
|
|
187
192
|
|
|
188
193
|
|
|
189
194
|
class PathTemplateWriter:
|
flow/record/version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '3.22.
|
|
32
|
-
__version_tuple__ = version_tuple = (3, 22, '
|
|
31
|
+
__version__ = version = '3.22.dev4'
|
|
32
|
+
__version_tuple__ = version_tuple = (3, 22, 'dev4')
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flow.record
|
|
3
|
-
Version: 3.22.
|
|
3
|
+
Version: 3.22.dev4
|
|
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-Expression: AGPL-3.0-or-later
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
flow/record/__init__.py,sha256=YdIbBF-QkR4YKE6zLz6AjY953EpR0aaESH8XTCwfdMo,2338
|
|
2
|
-
flow/record/base.py,sha256=
|
|
2
|
+
flow/record/base.py,sha256=DQ7K6X7Tn7NP2uN471pGFmOJRPxbMnymHKfiwFCCPnQ,40064
|
|
3
3
|
flow/record/context.py,sha256=2zpAucmR-RVzjXqBf6O4eq6nKMuIs03cacHPlTBvUPY,2009
|
|
4
4
|
flow/record/exceptions.py,sha256=_TLT3mWGO7b9KRxwP7FIz1a_nc9cizUuNNK5C3nPXWc,328
|
|
5
|
-
flow/record/jsonpacker.py,sha256=
|
|
5
|
+
flow/record/jsonpacker.py,sha256=pUEoO1Ag5r2Wi7KSOX8shiOgwx2d2L9ZGV_32OGiVD0,4356
|
|
6
6
|
flow/record/packer.py,sha256=lRDuf8r_mk-G2o2rU8dl9wbbuN8CnQ37q6k0pwbvFf0,6817
|
|
7
7
|
flow/record/selector.py,sha256=HHgLY7ImtNPgtD-wnywhymsAgNC3DNAaylhoau3nWCA,21980
|
|
8
|
-
flow/record/stream.py,sha256=
|
|
8
|
+
flow/record/stream.py,sha256=EGk_HMTCeDfsNavdMzFV_IrOdwtQKw3SNKQEJBoB3LU,11224
|
|
9
9
|
flow/record/utils.py,sha256=l2bxnVhzWqaBFaGz30vgCmUuVE4mo7ehQuxC3dC-ozM,4421
|
|
10
|
-
flow/record/version.py,sha256=
|
|
10
|
+
flow/record/version.py,sha256=kAeAhSSYdbUawpAl58lsxIATnUGGwaT4bTBpuO-FJE0,714
|
|
11
11
|
flow/record/whitelist.py,sha256=YyTAXMZSiYSzkXhASkrHDHgbpLC-tZaKAs7pfOADBcQ,775
|
|
12
12
|
flow/record/adapter/__init__.py,sha256=oTC0J7xXIxoCc3pEO8yUiIi5ZpVhKMulEYJ2FbQV8xY,1425
|
|
13
13
|
flow/record/adapter/archive.py,sha256=1RpKAPwIMD42x0j27zgFfOTW3xbhsc33QCXHyQxPg98,1112
|
|
@@ -25,7 +25,7 @@ flow/record/adapter/sqlite.py,sha256=Nmr8DYI7oLZA06u8FitndlKsPa96uHib8_V99gnrqwc
|
|
|
25
25
|
flow/record/adapter/stream.py,sha256=ZIG9KvG39zy0IJ0o_DTVEGOTcrdr5kRR8hf0wCynldg,1826
|
|
26
26
|
flow/record/adapter/text.py,sha256=PfCnQmb7FTSxjUghR6mUudAtBaSUY2ywW630rYic3aY,2038
|
|
27
27
|
flow/record/adapter/xlsx.py,sha256=D9g6asOTM1BvlOAOjQngP9bn8QHB8W-OH7L_kcI_2Ec,5706
|
|
28
|
-
flow/record/fieldtypes/__init__.py,sha256=
|
|
28
|
+
flow/record/fieldtypes/__init__.py,sha256=KP7VzWsxoW365P1BRUPf4JkL9VLWBkEHbYiIhdJLM3U,27976
|
|
29
29
|
flow/record/fieldtypes/credential.py,sha256=AjJsHmCdNxiDFUV-z81eu3NUGkbqN8QwhgFejPDVRHs,148
|
|
30
30
|
flow/record/fieldtypes/net/__init__.py,sha256=snPNt9_BEh5vGGQi0ZVSPHkwHTq2Qic0BKsGWNd92Qk,370
|
|
31
31
|
flow/record/fieldtypes/net/ip.py,sha256=AgfM11NyTIuYh_iXnzxfLHXHX1N1qewTlCibhICiFjg,3491
|
|
@@ -35,10 +35,10 @@ flow/record/fieldtypes/net/udp.py,sha256=_M85FWHktqPPBDr_aNzVMKAKZZwNEovrWmu-7yV
|
|
|
35
35
|
flow/record/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
36
|
flow/record/tools/geoip.py,sha256=yyTMTZldafz1yZ9F3IkiRH1_hFUyWPMQaMYvljQbKMk,5417
|
|
37
37
|
flow/record/tools/rdump.py,sha256=TRtcQsfJE3rNYb8D8NubVa4jgDzQX7gxm0B6_TzoXm0,14270
|
|
38
|
-
flow_record-3.22.
|
|
39
|
-
flow_record-3.22.
|
|
40
|
-
flow_record-3.22.
|
|
41
|
-
flow_record-3.22.
|
|
42
|
-
flow_record-3.22.
|
|
43
|
-
flow_record-3.22.
|
|
44
|
-
flow_record-3.22.
|
|
38
|
+
flow_record-3.22.dev4.dist-info/licenses/COPYRIGHT,sha256=MemuVEitZl6nTA3wCiJXY3v-Y5cYgfCzTodptjpZ9Tw,298
|
|
39
|
+
flow_record-3.22.dev4.dist-info/licenses/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
|
40
|
+
flow_record-3.22.dev4.dist-info/METADATA,sha256=_Dvd4-b5UGmvEp_fAxREYlNOS4Z9MsVSqG0Be1nGFd4,6214
|
|
41
|
+
flow_record-3.22.dev4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
42
|
+
flow_record-3.22.dev4.dist-info/entry_points.txt,sha256=BYPSrpFXabKCvE97dU3Bj4G_cfozaxsxsN7IHu6Vkpo,93
|
|
43
|
+
flow_record-3.22.dev4.dist-info/top_level.txt,sha256=mZX7nA6le1XNJV9ujgVL0yyesYwj3fMwHSm3zGwjNbU,5
|
|
44
|
+
flow_record-3.22.dev4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|