dissect.target 3.19.dev29__py3-none-any.whl → 3.19.dev31__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,11 +3,11 @@ from __future__ import annotations
3
3
  import io
4
4
  import textwrap
5
5
  from logging import getLogger
6
- from typing import Any, BinaryIO, Iterator, Optional, Union
6
+ from typing import Any, BinaryIO, Iterator, Optional
7
7
 
8
8
  from dissect.target import Target
9
9
  from dissect.target.exceptions import ConfigurationParsingError, FileNotFoundError
10
- from dissect.target.filesystem import Filesystem, FilesystemEntry, VirtualFilesystem
10
+ from dissect.target.filesystem import FilesystemEntry, VirtualFilesystem
11
11
  from dissect.target.helpers import fsutil
12
12
  from dissect.target.helpers.configutil import ConfigurationParser, parse
13
13
 
@@ -46,7 +46,7 @@ class ConfigurationFilesystem(VirtualFilesystem):
46
46
  super().__init__(**kwargs)
47
47
  self.root.top = target.fs.get(path)
48
48
 
49
- def _get_till_file(self, path: str, relentry: FilesystemEntry) -> tuple[list[str], FilesystemEntry]:
49
+ def _get_till_file(self, path: str, relentry: FilesystemEntry | None) -> tuple[list[str], FilesystemEntry]:
50
50
  """Searches for the file entry that is pointed to by ``path``.
51
51
 
52
52
  The ``path`` could contain ``key`` entries too, so it searches for the entry from
@@ -56,9 +56,13 @@ class ConfigurationFilesystem(VirtualFilesystem):
56
56
  A list of ``parts`` containing keys: [keys, into, the, file].
57
57
  And the resolved entry: Entry(filename)
58
58
  """
59
+
59
60
  entry = relentry or self.root
61
+ root_path = relentry.path if relentry else self.root.top.path
60
62
 
61
- path = fsutil.normalize(path, alt_separator=self.alt_separator).strip("/")
63
+ # Calculate the relative path
64
+ relpath = fsutil.relpath(path, root_path, alt_separator=self.alt_separator)
65
+ path = fsutil.normalize(relpath, alt_separator=self.alt_separator).strip("/")
62
66
 
63
67
  if not path:
64
68
  return [], entry
@@ -85,10 +89,8 @@ class ConfigurationFilesystem(VirtualFilesystem):
85
89
 
86
90
  return parts[idx:], entry
87
91
 
88
- def get(
89
- self, path: str, relentry: Optional[FilesystemEntry] = None, *args, **kwargs
90
- ) -> Union[FilesystemEntry, ConfigurationEntry]:
91
- """Retrieve a :class:`ConfigurationEntry` or :class:`.FilesystemEntry` relative to the root or ``relentry``.
92
+ def get(self, path: str, relentry: Optional[FilesystemEntry] = None, *args, **kwargs) -> ConfigurationEntry:
93
+ """Retrieve a :class:`ConfigurationEntry` relative to the root or ``relentry``.
92
94
 
93
95
  Raises:
94
96
  FileNotFoundError: if it could not find the entry.
@@ -96,7 +98,7 @@ class ConfigurationFilesystem(VirtualFilesystem):
96
98
  parts, entry = self._get_till_file(path, relentry)
97
99
 
98
100
  if entry.is_dir():
99
- return entry
101
+ return ConfigurationEntry(self, entry.path, entry, None)
100
102
 
101
103
  entry = self._convert_entry(entry, *args, **kwargs)
102
104
 
@@ -108,23 +110,21 @@ class ConfigurationFilesystem(VirtualFilesystem):
108
110
 
109
111
  return entry
110
112
 
111
- def _convert_entry(
112
- self, file_entry: FilesystemEntry, *args, **kwargs
113
- ) -> Union[ConfigurationEntry, FilesystemEntry]:
113
+ def _convert_entry(self, file_entry: FilesystemEntry, *args, **kwargs) -> ConfigurationEntry:
114
114
  """Creates a :class:`ConfigurationEntry` from a ``file_entry``.
115
115
 
116
116
  If an error occurs during the parsing of the file contents,
117
117
  the original ``file_entry`` is returned.
118
118
  """
119
119
  entry = file_entry
120
+ config_parser = None
120
121
  try:
121
122
  config_parser = parse(entry, *args, **kwargs)
122
- entry = ConfigurationEntry(self, entry.path, entry, config_parser)
123
123
  except ConfigurationParsingError as e:
124
124
  # If a parsing error gets created, it should return the `entry`
125
125
  log.debug("Error when parsing %s with message '%s'", entry.path, e)
126
126
 
127
- return entry
127
+ return ConfigurationEntry(self, entry.path, entry, config_parser)
128
128
 
129
129
 
130
130
  class ConfigurationEntry(FilesystemEntry):
@@ -163,10 +163,10 @@ class ConfigurationEntry(FilesystemEntry):
163
163
 
164
164
  def __init__(
165
165
  self,
166
- fs: Filesystem,
166
+ fs: ConfigurationFilesystem,
167
167
  path: str,
168
168
  entry: FilesystemEntry,
169
- parser_items: Optional[Union[dict, ConfigurationParser, str, list]] = None,
169
+ parser_items: dict | ConfigurationParser | str | list | None = None,
170
170
  ) -> None:
171
171
  super().__init__(fs, path, entry)
172
172
  self.parser_items = parser_items
@@ -182,7 +182,7 @@ class ConfigurationEntry(FilesystemEntry):
182
182
 
183
183
  return f"<{self.__class__.__name__} {output}"
184
184
 
185
- def get(self, key, default: Optional[Any] = None) -> Union[ConfigurationEntry, Any, None]:
185
+ def get(self, key, default: Any | None = None) -> ConfigurationEntry | Any | None:
186
186
  """Gets the dictionary key that belongs to this entry using ``key``.
187
187
  Behaves like ``dictionary.get()``.
188
188
 
@@ -197,13 +197,19 @@ class ConfigurationEntry(FilesystemEntry):
197
197
  if not key:
198
198
  raise TypeError("key should be defined")
199
199
 
200
- if key in self.parser_items:
200
+ path = fsutil.join(self.path, key, alt_separator=self.fs.alt_separator)
201
+
202
+ if self.parser_items and key in self.parser_items:
201
203
  return ConfigurationEntry(
202
204
  self.fs,
203
- fsutil.join(self.path, key, alt_separator=self.fs.alt_separator),
205
+ path,
204
206
  self.entry,
205
207
  self.parser_items[key],
206
208
  )
209
+
210
+ if self.entry.is_dir():
211
+ return self.fs.get(path, self.entry)
212
+
207
213
  return default
208
214
 
209
215
  def _write_value_mapping(self, values: dict[str, Any], indentation_nr: int = 0) -> str:
@@ -257,6 +263,11 @@ class ConfigurationEntry(FilesystemEntry):
257
263
  if self.is_file():
258
264
  raise NotADirectoryError()
259
265
 
266
+ if self.parser_items is None and self.entry.is_dir():
267
+ for entry in self.entry.scandir():
268
+ yield ConfigurationEntry(self.fs, entry.name, entry, None)
269
+ return
270
+
260
271
  for key, values in self.parser_items.items():
261
272
  yield ConfigurationEntry(self.fs, key, self.entry, values)
262
273
 
@@ -266,7 +277,7 @@ class ConfigurationEntry(FilesystemEntry):
266
277
  def is_dir(self, follow_symlinks: bool = True) -> bool:
267
278
  """Returns whether this :class:`ConfigurationEntry` can be considered a directory."""
268
279
  # if self.parser_items has keys (thus sub-values), we can consider it a directory.
269
- return hasattr(self.parser_items, "keys")
280
+ return (self.parser_items is None and self.entry.is_dir()) or hasattr(self.parser_items, "keys")
270
281
 
271
282
  def is_symlink(self) -> bool:
272
283
  """Return whether this :class:`ConfigurationEntry` is a symlink or not.
@@ -284,7 +295,7 @@ class ConfigurationEntry(FilesystemEntry):
284
295
  Returns:
285
296
  Whether the ``entry`` and ``key`` exists
286
297
  """
287
- return self.entry.exists() and key in self.parser_items
298
+ return self.parser_items and self.entry.exists() and key in self.parser_items
288
299
 
289
300
  def stat(self, follow_symlinks: bool = True) -> fsutil.stat_result:
290
301
  """Returns the stat from the underlying :class:`.FilesystemEntry` :attr:`entry`."""
@@ -171,7 +171,7 @@ class ConfigurationParser:
171
171
  try:
172
172
  self.parse_file(fh)
173
173
  except Exception as e:
174
- raise ConfigurationParsingError(*e.args) from e
174
+ raise ConfigurationParsingError(e.args) from e
175
175
 
176
176
  if self.collapse_all or self.collapse:
177
177
  self.parsed_data = self._collapse_dict(self.parsed_data)
@@ -8,7 +8,7 @@ import subprocess
8
8
  from configparser import ConfigParser
9
9
  from configparser import Error as ConfigParserError
10
10
  from io import BytesIO
11
- from typing import Any, BinaryIO, Iterator, Optional, TextIO
11
+ from typing import Any, BinaryIO, Iterator, TextIO
12
12
 
13
13
  from defusedxml import ElementTree
14
14
  from dissect.hypervisor.util import vmtar
@@ -73,7 +73,7 @@ class ESXiPlugin(UnixPlugin):
73
73
  if configstore.exists():
74
74
  self._configstore = parse_config_store(configstore.open())
75
75
 
76
- def _cfg(self, path: str) -> Optional[str]:
76
+ def _cfg(self, path: str) -> str | None:
77
77
  if not self._config:
78
78
  self.target.log.warning("No ESXi config!")
79
79
  return None
@@ -87,7 +87,7 @@ class ESXiPlugin(UnixPlugin):
87
87
  return obj.get(value_name) if obj else None
88
88
 
89
89
  @classmethod
90
- def detect(cls, target: Target) -> Optional[Filesystem]:
90
+ def detect(cls, target: Target) -> Filesystem | None:
91
91
  bootbanks = [
92
92
  fs for fs in target.filesystems if fs.path("boot.cfg").exists() and list(fs.path("/").glob("*.v00"))
93
93
  ]
@@ -128,7 +128,7 @@ class ESXiPlugin(UnixPlugin):
128
128
  return "localhost"
129
129
 
130
130
  @export(property=True)
131
- def domain(self) -> Optional[str]:
131
+ def domain(self) -> str | None:
132
132
  if hostname := self._cfg("/adv/Misc/HostName"):
133
133
  return hostname.partition(".")[2]
134
134
 
@@ -146,7 +146,7 @@ class ESXiPlugin(UnixPlugin):
146
146
  return list(result)
147
147
 
148
148
  @export(property=True)
149
- def version(self) -> Optional[str]:
149
+ def version(self) -> str | None:
150
150
  boot_cfg = self.target.fs.path("/bootbank/boot.cfg")
151
151
  if not boot_cfg.exists():
152
152
  return None
@@ -187,11 +187,11 @@ class ESXiPlugin(UnixPlugin):
187
187
  return self._configstore
188
188
 
189
189
  @export(property=True)
190
- def os(self):
190
+ def os(self) -> str:
191
191
  return OperatingSystem.ESXI.value
192
192
 
193
193
 
194
- def _mount_modules(target: Target, sysvol: Filesystem, cfg: dict[str, str]):
194
+ def _mount_modules(target: Target, sysvol: Filesystem, cfg: dict[str, str]) -> None:
195
195
  modules = [m.strip() for m in cfg["modules"].split("---")]
196
196
 
197
197
  for module in modules:
@@ -218,20 +218,22 @@ def _mount_modules(target: Target, sysvol: Filesystem, cfg: dict[str, str]):
218
218
  target.fs.append_layer().mount("/", tfs)
219
219
 
220
220
 
221
- def _mount_local(target: Target, local_layer: VirtualFilesystem):
221
+ def _mount_local(target: Target, local_layer: VirtualFilesystem) -> None:
222
222
  local_tgz = target.fs.path("local.tgz")
223
+ local_tgz_ve = target.fs.path("local.tgz.ve")
223
224
  local_fs = None
224
225
 
225
226
  if local_tgz.exists():
226
227
  local_fs = tar.TarFilesystem(local_tgz.open())
227
- else:
228
- local_tgz_ve = target.fs.path("local.tgz.ve")
228
+ elif local_tgz_ve.exists():
229
229
  # In the case "encryption.info" does not exist, but ".#encryption.info" does
230
230
  encryption_info = next(target.fs.path("/").glob("*encryption.info"), None)
231
231
  if not local_tgz_ve.exists() or not encryption_info.exists():
232
232
  raise ValueError("Unable to find valid configuration archive")
233
233
 
234
234
  local_fs = _create_local_fs(target, local_tgz_ve, encryption_info)
235
+ else:
236
+ target.log.warning("No local.tgz or local.tgz.ve found, skipping local state")
235
237
 
236
238
  if local_fs:
237
239
  local_layer.mount("/", local_fs)
@@ -245,7 +247,7 @@ def _decrypt_envelope(local_tgz_ve: TargetPath, encryption_info: TargetPath) ->
245
247
  return local_tgz
246
248
 
247
249
 
248
- def _decrypt_crypto_util(local_tgz_ve: TargetPath) -> Optional[BytesIO]:
250
+ def _decrypt_crypto_util(local_tgz_ve: TargetPath) -> BytesIO | None:
249
251
  """Decrypt ``local.tgz.ve`` using ESXi ``crypto-util``.
250
252
 
251
253
  We write to stdout, but this results in ``crypto-util`` exiting with a non-zero return code
@@ -264,9 +266,7 @@ def _decrypt_crypto_util(local_tgz_ve: TargetPath) -> Optional[BytesIO]:
264
266
  return BytesIO(result.stdout)
265
267
 
266
268
 
267
- def _create_local_fs(
268
- target: Target, local_tgz_ve: TargetPath, encryption_info: TargetPath
269
- ) -> Optional[tar.TarFilesystem]:
269
+ def _create_local_fs(target: Target, local_tgz_ve: TargetPath, encryption_info: TargetPath) -> tar.TarFilesystem | None:
270
270
  local_tgz = None
271
271
 
272
272
  if HAS_ENVELOPE:
@@ -292,7 +292,7 @@ def _create_local_fs(
292
292
  return tar.TarFilesystem(local_tgz)
293
293
 
294
294
 
295
- def _mount_filesystems(target: Target, sysvol: Filesystem, cfg: dict[str, str]):
295
+ def _mount_filesystems(target: Target, sysvol: Filesystem, cfg: dict[str, str]) -> None:
296
296
  version = cfg["build"]
297
297
 
298
298
  osdata_fs = None
@@ -371,7 +371,7 @@ def _mount_filesystems(target: Target, sysvol: Filesystem, cfg: dict[str, str]):
371
371
  target.fs.symlink(f"/vmfs/volumes/LOCKER-{locker_fs.vmfs.uuid}", "/locker")
372
372
 
373
373
 
374
- def _link_log_dir(target: Target, cfg: dict[str, str], plugin_obj: ESXiPlugin):
374
+ def _link_log_dir(target: Target, cfg: dict[str, str], plugin_obj: ESXiPlugin) -> None:
375
375
  version = cfg["build"]
376
376
 
377
377
  # Don't really know how ESXi does this, but let's just take a shortcut for now
@@ -441,7 +441,7 @@ def parse_esx_conf(fh: TextIO) -> dict[str, Any]:
441
441
  return config
442
442
 
443
443
 
444
- def _traverse(path: str, obj: dict[str, Any], create: bool = False):
444
+ def _traverse(path: str, obj: dict[str, Any], create: bool = False) -> dict[str, Any] | None:
445
445
  parts = path.strip("/").split("/")
446
446
  path_parts = parts[:-1]
447
447
  for part in path_parts:
@@ -62,13 +62,13 @@ class EtcTree(ConfigurationTreePlugin):
62
62
 
63
63
  @export(record=UnixConfigTreeRecord)
64
64
  @arg("--glob", dest="pattern", required=False, default="*", type=str, help="Glob-style pattern to search for")
65
- def etc(self, pattern: str) -> Iterator[UnixConfigTreeRecord]:
66
- for entry, subs, items in self.config_fs.walk("/"):
65
+ @arg("--root", dest="root", required=False, default="/", type=str, help="Path to use as root for search")
66
+ def etc(self, pattern: str, root: str) -> Iterator[UnixConfigTreeRecord]:
67
+ for entry, subs, items in self.config_fs.walk(root):
67
68
  for item in items:
68
69
  try:
69
70
  config_object = self.get(str(Path(entry) / Path(item)))
70
- if isinstance(config_object, ConfigurationEntry):
71
- yield from self._sub(config_object, Path(entry) / Path(item), pattern)
71
+ yield from self._sub(config_object, Path(entry) / Path(item), pattern)
72
72
  except Exception:
73
73
  self.target.log.warning("Could not open configuration item: %s", item)
74
74
  pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.target
3
- Version: 3.19.dev29
3
+ Version: 3.19.dev31
4
4
  Summary: This module ties all other Dissect modules together, it provides a programming API and command line tools which allow easy access to various data sources inside disk images or file collections (a.k.a. targets)
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Affero General Public License v3
@@ -25,7 +25,7 @@ dissect/target/filesystems/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
25
25
  dissect/target/filesystems/ad1.py,sha256=nEPzaaRsb6bL4ItFo0uLdmdLvrmK9BjqHeD3FOp3WQI,2413
26
26
  dissect/target/filesystems/btrfs.py,sha256=5MBi193ZvclkEQcxDr_sDHfj_FYU_hyYNRL4YqpDu4M,6243
27
27
  dissect/target/filesystems/cb.py,sha256=6LcoJiwsYu1Han31IUzVpZVDTifhTLTx_gLfNpB_p6k,5329
28
- dissect/target/filesystems/config.py,sha256=C2JnzBzMqbAjchGFDwURItCeUY7uxkhw1Gen-6cGkAc,11432
28
+ dissect/target/filesystems/config.py,sha256=GQOtixIIt-Jjtpl3IVqUTujcBFfWaAZeAtvxgNUNetU,11963
29
29
  dissect/target/filesystems/cpio.py,sha256=ssVCjkAtLn2FqmNxeo6U5boyUdSYFxLWfXpytHYGPqs,641
30
30
  dissect/target/filesystems/dir.py,sha256=rKEreX3A7CI6a3pMssrO9F-9i5pkxCn_Ucs_dMtHxxA,4574
31
31
  dissect/target/filesystems/exfat.py,sha256=PRkZPUVN5NlgB1VetFtywdNgF6Yj5OBtF5a25t-fFvw,5917
@@ -46,7 +46,7 @@ dissect/target/filesystems/zip.py,sha256=WT1bQhzX_1MXXVZTKrJniae4xqRqMZ8FsfbvhgG
46
46
  dissect/target/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
47
  dissect/target/helpers/cache.py,sha256=TXlJBdFRz6V9zKs903am4Yawr0maYw5kZY0RqklDQJM,8568
48
48
  dissect/target/helpers/config.py,sha256=6917CZ6eDHaK_tOoiVEIndyhRXO6r6eCBIleq6f47PQ,2346
49
- dissect/target/helpers/configutil.py,sha256=SLJFt_k5ezm7CIr_mcZqVrV-2YrcX5D48KgyC9oyWGw,27645
49
+ dissect/target/helpers/configutil.py,sha256=AEnkMQ0e6PncvCqGa-ACzBQWQBhMGBCzO5qzGJtRu60,27644
50
50
  dissect/target/helpers/cyber.py,sha256=WnJlk-HqAETmDAgLq92JPxyDLxvzSoFV_WrO-odVKBI,16805
51
51
  dissect/target/helpers/descriptor_extensions.py,sha256=uT8GwznfDAiIgMM7JKKOY0PXKMv2c0GCqJTCkWFgops,2605
52
52
  dissect/target/helpers/docs.py,sha256=J5U65Y3yOTqxDEZRCdrEmO63XQCeDzOJea1PwPM6Cyc,5146
@@ -208,9 +208,9 @@ dissect/target/plugins/os/unix/bsd/osx/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JC
208
208
  dissect/target/plugins/os/unix/bsd/osx/_os.py,sha256=KvP7YJ7apVwoIop7MR-8q5QbVGoB6MdR42l6ssEe6es,4081
209
209
  dissect/target/plugins/os/unix/bsd/osx/user.py,sha256=qopB0s3n7e6Q7NjWzn8Z-dKtDtU7e6In4Vm7hIvvedo,2322
210
210
  dissect/target/plugins/os/unix/esxi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
211
- dissect/target/plugins/os/unix/esxi/_os.py,sha256=JOJ6j57vFCojgBNkju-7MG2nQqwl4Qc-agXTwjhZPgY,17644
211
+ dissect/target/plugins/os/unix/esxi/_os.py,sha256=s6pAgUyfHh3QcY6sgvk5uVMmLvqK1tIHWR7MSbrFn8w,17789
212
212
  dissect/target/plugins/os/unix/etc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
213
- dissect/target/plugins/os/unix/etc/etc.py,sha256=WNCtO7NWOKRDBiV-XjXqgPuGRDE_Zyw6XWz3kTm__QE,2493
213
+ dissect/target/plugins/os/unix/etc/etc.py,sha256=px_UwtPuk_scD-3nKJQZ0ao5lus9-BrSU4lPZWelYzI,2541
214
214
  dissect/target/plugins/os/unix/linux/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
215
215
  dissect/target/plugins/os/unix/linux/_os.py,sha256=n6VkfGYIdZUxcK2C1aPDUY_ZZQEIl0GkrpvIKeguv5o,2812
216
216
  dissect/target/plugins/os/unix/linux/cmdline.py,sha256=AyMfndt3UsmJtoOyZYC8nWq2GZg9oPvn8SiI3M4NxnE,1622
@@ -346,10 +346,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
346
346
  dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
347
347
  dissect/target/volumes/md.py,sha256=7ShPtusuLGaIv27SvEETtgsuoQyAa4iAAeOR1NEaajI,1689
348
348
  dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
349
- dissect.target-3.19.dev29.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
350
- dissect.target-3.19.dev29.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
351
- dissect.target-3.19.dev29.dist-info/METADATA,sha256=mE32WDUFrARni26ZPIFtiWS3iHI51FCZBoIz4IVFVL4,12719
352
- dissect.target-3.19.dev29.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
353
- dissect.target-3.19.dev29.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
354
- dissect.target-3.19.dev29.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
355
- dissect.target-3.19.dev29.dist-info/RECORD,,
349
+ dissect.target-3.19.dev31.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
350
+ dissect.target-3.19.dev31.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
351
+ dissect.target-3.19.dev31.dist-info/METADATA,sha256=GZuNZDNA6Y6mINzf5sqR_gX4uyq0i5TLvFhCse-bxPc,12719
352
+ dissect.target-3.19.dev31.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
353
+ dissect.target-3.19.dev31.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
354
+ dissect.target-3.19.dev31.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
355
+ dissect.target-3.19.dev31.dist-info/RECORD,,