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 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
- executable: path | None = None
756
- args: list[str] | None = None
755
+ """The command fieldtype splits a command string into an ``executable`` and its arguments.
757
756
 
758
- _path_type: type[path] = None
759
- _posix: bool
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
- def __new__(cls, value: str):
762
- if cls is not command:
763
- return super().__new__(cls)
761
+ Example:
764
762
 
765
- if not isinstance(value, str):
766
- raise TypeError(f"Expected a value of type 'str' not {type(value)}")
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
- # pre checking for windows like paths
769
- # This checks for windows like starts of a path:
770
- # an '%' for an environment variable
771
- # r'\\' for a UNC path
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
- cls = windows_command if windows else posix_command
777
- return super().__new__(cls)
773
+ __executable: path
774
+ __args: tuple[str, ...]
778
775
 
779
- def __init__(self, value: str | tuple[str, tuple[str]] | None):
780
- if value is None:
781
- return
776
+ __path_type: type[path]
782
777
 
783
- if isinstance(value, str):
784
- self.executable, self.args = self._split(value)
785
- return
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
- executable, self.args = value
788
- self.executable = self._path_type(executable)
789
- self.args = list(self.args)
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._join() == other
796
+ return self.raw == other
799
797
  if isinstance(other, (tuple, list)):
800
- return self.executable == other[0] and self.args == list(other[1:])
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, list[str]]:
805
- executable, *args = shlex.split(value, posix=self._posix)
806
- executable = executable.strip("'\" ")
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
- def _join(self) -> str:
811
- return shlex.join([str(self.executable), *self.args])
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[tuple[str, list], str]:
814
- command_type = TYPE_WINDOWS if isinstance(self, windows_command) else TYPE_POSIX
815
- if self.executable:
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 from_posix(cls, value: str) -> command:
830
- return posix_command(value)
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
- @classmethod
833
- def from_windows(cls, value: str) -> command:
834
- return windows_command(value)
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
- class posix_command(command):
838
- _posix = True
839
- _path_type = posix_path
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
- class windows_command(command):
843
- _posix = False
844
- _path_type = windows_path
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
- def _split(self, value: str) -> tuple[str, list[str]]:
847
- executable, args = super()._split(value)
848
- if args:
849
- args = [" ".join(args)]
848
+ @property
849
+ def raw(self) -> str:
850
+ exe = str(self.executable)
850
851
 
851
- return executable, args
852
+ if " " in exe:
853
+ exe = shlex.quote(exe)
852
854
 
853
- def _join(self) -> str:
854
- arg = f" {self.args[0]}" if self.args else ""
855
- executable_str = str(self.executable)
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
- if " " in executable_str:
858
- return f"'{executable_str}'{arg}"
863
+ @classmethod
864
+ def from_posix(cls, value: str) -> command:
865
+ return command(value, path_type=posix_path)
859
866
 
860
- return f"{executable_str}{arg}"
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
- reader.close()
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.dev2'
32
- __version_tuple__ = version_tuple = (3, 22, 'dev2')
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.dev2
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=Q4BsZvbRrumYsdMvFoFdyrR2FSt3lKENIEn2B5RRlDM,39965
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=VrlIND4pQcBGJyP_pjrkV70ldDQuzh1oblHR4hjkuHM,4444
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=R5kB8yhDLWua1cP-9bcZnAL1d2AOSPdBp3S2YYlGS7w,10983
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=T4NYT52v19tqWhTJmaoeG2ZbVmBQ-ApSBVdSQCTNuXA,714
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=7uipEnCvR3IuuF6rg4KvGHgbQ9wUNAZYbRO5h5cPxfI,27386
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.dev2.dist-info/licenses/COPYRIGHT,sha256=MemuVEitZl6nTA3wCiJXY3v-Y5cYgfCzTodptjpZ9Tw,298
39
- flow_record-3.22.dev2.dist-info/licenses/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
40
- flow_record-3.22.dev2.dist-info/METADATA,sha256=CMiqBNPebM2yjgRTQVyl4nvyg2K3nydF1rGb6MQ4DoQ,6214
41
- flow_record-3.22.dev2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
42
- flow_record-3.22.dev2.dist-info/entry_points.txt,sha256=BYPSrpFXabKCvE97dU3Bj4G_cfozaxsxsN7IHu6Vkpo,93
43
- flow_record-3.22.dev2.dist-info/top_level.txt,sha256=mZX7nA6le1XNJV9ujgVL0yyesYwj3fMwHSm3zGwjNbU,5
44
- flow_record-3.22.dev2.dist-info/RECORD,,
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,,