dissect.cim 3.10.dev3__tar.gz → 3.11.dev2__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.
Files changed (36) hide show
  1. dissect_cim-3.11.dev2/.git-blame-ignore-revs +6 -0
  2. {dissect_cim-3.10.dev3/dissect.cim.egg-info → dissect_cim-3.11.dev2}/PKG-INFO +2 -2
  3. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/dissect/cim/c_cim.py +12 -3
  4. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/dissect/cim/cim.py +115 -90
  5. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/dissect/cim/classes.py +95 -83
  6. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/dissect/cim/index.py +57 -44
  7. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/dissect/cim/mappings.py +21 -17
  8. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/dissect/cim/objects.py +20 -12
  9. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/dissect/cim/utils.py +36 -30
  10. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2/dissect.cim.egg-info}/PKG-INFO +2 -2
  11. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/dissect.cim.egg-info/SOURCES.txt +1 -0
  12. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/pyproject.toml +48 -5
  13. dissect_cim-3.11.dev2/tests/conftest.py +34 -0
  14. dissect_cim-3.11.dev2/tests/test_cim.py +10 -0
  15. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/tox.ini +4 -10
  16. dissect_cim-3.10.dev3/tests/conftest.py +0 -28
  17. dissect_cim-3.10.dev3/tests/test_cim.py +0 -6
  18. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/COPYRIGHT +0 -0
  19. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/LICENSE +0 -0
  20. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/MANIFEST.in +0 -0
  21. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/README.md +0 -0
  22. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/dissect/cim/__init__.py +0 -0
  23. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/dissect/cim/exceptions.py +0 -0
  24. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/dissect.cim.egg-info/dependency_links.txt +0 -0
  25. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/dissect.cim.egg-info/requires.txt +0 -0
  26. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/dissect.cim.egg-info/top_level.txt +0 -0
  27. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/setup.cfg +0 -0
  28. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/tests/__init__.py +0 -0
  29. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/tests/data/INDEX.BTR.gz +0 -0
  30. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/tests/data/MAPPING1.MAP.gz +0 -0
  31. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/tests/data/MAPPING2.MAP.gz +0 -0
  32. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/tests/data/MAPPING3.MAP.gz +0 -0
  33. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/tests/data/OBJECTS.DATA.gz +0 -0
  34. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/tests/docs/Makefile +0 -0
  35. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/tests/docs/conf.py +0 -0
  36. {dissect_cim-3.10.dev3 → dissect_cim-3.11.dev2}/tests/docs/index.rst +0 -0
@@ -0,0 +1,6 @@
1
+ # Formatting commits. You can ignore them during git-blame with `--ignore-rev` or `--ignore-revs-file`.
2
+ #
3
+ # $ git config --add 'blame.ignoreRevsFile' '.git-blame-ignore-revs'
4
+ #
5
+ # Change linter to Ruff (#27)
6
+ 7398596e3032b7f1aca19126d40e63da9a0c5847
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: dissect.cim
3
- Version: 3.10.dev3
3
+ Version: 3.11.dev2
4
4
  Summary: A Dissect module implementing a parser for the Windows Common Information Model (CIM) database, used in the Windows operating system
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Affero General Public License v3
@@ -1,4 +1,4 @@
1
- from collections import namedtuple
1
+ from typing import NamedTuple, Union
2
2
 
3
3
  from dissect.cstruct import cstruct
4
4
 
@@ -240,5 +240,14 @@ CIM_TYPES_MAP = {
240
240
  CIM_TYPES.CHAR16: c_cim.wchar,
241
241
  }
242
242
 
243
- ClassDefinitionPropertyState = namedtuple("ClassDefinitionPropertyState", ["is_inherited", "has_default_value"])
244
- ClassInstancePropertyState = namedtuple("ClassInstancePropertyState", ["use_default_value", "is_initialized"])
243
+ CimType = Union[int, float, str, bool]
244
+
245
+
246
+ class ClassDefinitionPropertyState(NamedTuple):
247
+ is_inherited: bool
248
+ has_default_value: bool
249
+
250
+
251
+ class ClassInstancePropertyState(NamedTuple):
252
+ use_default_value: bool
253
+ is_initialized: bool
@@ -3,28 +3,45 @@
3
3
  # Information about e.g. data structures can also be found in:
4
4
  # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wmio/b44d0581-5bd3-40fc-95d7-01c1b1239820
5
5
 
6
- import os
6
+ from __future__ import annotations
7
+
8
+ from functools import cached_property
7
9
  from io import BytesIO
10
+ from pathlib import Path
11
+ from typing import TYPE_CHECKING, Any, BinaryIO
8
12
 
9
13
  from dissect.cim.c_cim import (
10
14
  ARRAY_STATES,
11
15
  NAMESPACE_CLASS_NAME,
12
16
  ROOT_NAMESPACE_NAME,
13
17
  SYSTEM_NAMESPACE_NAME,
18
+ CimType,
14
19
  c_cim,
15
20
  )
16
- from dissect.cim.classes import ClassDefinition, ClassInstance, PropertyDefaultValues
21
+ from dissect.cim.classes import (
22
+ ClassDefinition,
23
+ ClassDefinitionProperty,
24
+ ClassInstance,
25
+ InstanceKey,
26
+ PropertyDefaultValues,
27
+ Qualifier,
28
+ )
17
29
  from dissect.cim.exceptions import Error, InvalidDatabaseError
18
30
  from dissect.cim.index import Index, Key
19
31
  from dissect.cim.mappings import Mapping
20
32
  from dissect.cim.objects import Objects
21
33
  from dissect.cim.utils import find_current_mapping, parse_object_path
22
34
 
35
+ if TYPE_CHECKING:
36
+ from collections.abc import Iterator
37
+
38
+ from dissect.cstruct import BaseType
39
+
23
40
 
24
41
  class CIM:
25
- """Common Information Model"""
42
+ """Common Information Model."""
26
43
 
27
- def __init__(self, index, objects, mappings):
44
+ def __init__(self, index: BinaryIO, objects: BinaryIO, mappings: list[BinaryIO]):
28
45
  self._findex = index
29
46
  self._fobjects = objects
30
47
  self._fmappings = mappings
@@ -62,18 +79,24 @@ class CIM:
62
79
  self.system = self.namespace(SYSTEM_NAMESPACE_NAME)
63
80
 
64
81
  @classmethod
65
- def from_directory(cls, path):
66
- path = os.path.abspath(path)
67
- findex = open(os.path.join(path, "INDEX.BTR"), "rb")
68
- fobjects = open(os.path.join(path, "OBJECTS.DATA"), "rb")
69
- fmappings = [open(os.path.join(path, f"MAPPING{i}.MAP"), "rb") for i in range(1, 4)]
82
+ def from_directory(cls, path: Path | str) -> CIM:
83
+ if not isinstance(path, Path):
84
+ path = Path(path)
85
+
86
+ path = path.resolve()
87
+ if not path.is_dir():
88
+ raise ValueError(f"Path {path} is not a directory")
89
+
90
+ findex = path.joinpath("INDEX.BTR").open("rb")
91
+ fobjects = path.joinpath("OBJECTS.DATA").open("rb")
92
+ fmappings = [path.joinpath(f"MAPPING{i}.MAP").open("rb") for i in range(1, 4)]
70
93
 
71
94
  return cls(findex, fobjects, fmappings)
72
95
 
73
- def key(self, *args):
96
+ def key(self, *args) -> Key:
74
97
  return Key(self, *args)
75
98
 
76
- def query(self, path, ns=None):
99
+ def query(self, path: str, ns: Namespace | None = None) -> Namespace | Class | Instance:
77
100
  if ns is not None and not isinstance(ns, Namespace):
78
101
  raise TypeError("namespace should be an instance of Namespace")
79
102
  object_path = parse_object_path(path, ns)
@@ -94,48 +117,48 @@ class CIM:
94
117
 
95
118
  return obj
96
119
 
97
- def namespace(self, name):
120
+ def namespace(self, name: str) -> Namespace:
98
121
  return Namespace(self, name)
99
122
 
100
- def _parse_instance(self, class_, buf):
123
+ def _parse_instance(self, class_: Class, buf: BytesIO) -> ClassInstance:
101
124
  return ClassInstance(self, class_, buf)
102
125
 
103
- def get_class_definition(self, q):
126
+ def get_class_definition(self, q: Key) -> ClassDefinition:
104
127
  if not q.reference():
105
128
  q = self.key().NS(SYSTEM_NAMESPACE_NAME).CD(q["CD"])
106
129
  return ClassDefinition(self, q.object())
107
130
 
108
- def get_class_instance(self, class_, q):
131
+ def get_class_instance(self, class_: Class, q: Key) -> ClassInstance:
109
132
  return self._parse_instance(class_, q.object())
110
133
 
111
134
 
112
135
  class Namespace:
113
- def __init__(self, cim, name, class_instance=None):
136
+ def __init__(self, cim: CIM, name: str, class_instance: ClassInstance | None = None):
114
137
  self.cim = cim
115
138
  self.name = name
116
139
  self.class_instance = class_instance
117
140
 
118
- def __repr__(self):
141
+ def __repr__(self) -> str:
119
142
  return f"<Namespace {self.name}>"
120
143
 
121
- def query(self, path):
144
+ def query(self, path: str) -> Namespace | Class | Instance:
122
145
  return self.cim.query(path, self)
123
146
 
124
147
  @property
125
- def ci(self):
148
+ def ci(self) -> ClassInstance | None:
126
149
  return self.class_instance
127
150
 
128
- def parent(self):
129
- raise NotImplementedError()
151
+ def parent(self) -> Namespace:
152
+ raise NotImplementedError
130
153
 
131
- def class_(self, class_name):
154
+ def class_(self, class_name: str) -> Class:
132
155
  q = self.cim.key().NS(self.name).CD(class_name)
133
156
  class_def = self.cim.get_class_definition(q)
134
157
 
135
158
  return Class(self.cim, self, class_def)
136
159
 
137
160
  @property
138
- def classes(self):
161
+ def classes(self) -> Iterator[Class]:
139
162
  yielded = set()
140
163
 
141
164
  if self.name != SYSTEM_NAMESPACE_NAME:
@@ -154,15 +177,16 @@ class Namespace:
154
177
  yield class_
155
178
  yielded.add(class_.name)
156
179
 
157
- def namespace(self, name):
158
- main_name = "\\".join([self.name, name]).lower()
159
- for name_spc in self.namespaces:
160
- if name_spc.name.lower() == main_name:
161
- return name_spc
162
- raise IndexError()
180
+ def namespace(self, name: str) -> Namespace:
181
+ main_name = f"{self.name}\\{name}".lower()
182
+ for ns in self.namespaces:
183
+ if ns.name.lower() == main_name:
184
+ return ns
185
+
186
+ raise IndexError
163
187
 
164
188
  @property
165
- def namespaces(self):
189
+ def namespaces(self) -> Iterator[Namespace]:
166
190
  yielded = set()
167
191
 
168
192
  q = self.cim.key().NS(self.name).CI(NAMESPACE_CLASS_NAME).IL()
@@ -170,7 +194,7 @@ class Namespace:
170
194
 
171
195
  for ref in q.references():
172
196
  class_instance = self.cim.get_class_instance(class_def, ref)
173
- ns = Namespace(self.cim, "\\".join([self.name, class_instance.properties["Name"].value]), class_instance)
197
+ ns = Namespace(self.cim, f"{self.name}\\{class_instance.properties['Name'].value}", class_instance)
174
198
 
175
199
  if ns.name not in yielded:
176
200
  yield ns
@@ -181,36 +205,34 @@ class Namespace:
181
205
 
182
206
 
183
207
  class Class:
184
- def __init__(self, cim, namespace, class_definition):
208
+ def __init__(self, cim: CIM, namespace: Namespace, class_definition: ClassDefinition):
185
209
  self.cim = cim
186
210
  self.namespace = namespace
187
211
  self.class_definition = class_definition
188
212
 
189
213
  self._properties = None
190
214
 
191
- def __getattr__(self, attr):
215
+ def __getattr__(self, attr: str) -> Any:
192
216
  try:
193
217
  return getattr(self.class_definition, attr)
194
218
  except AttributeError:
195
219
  return object.__getattribute__(self, attr)
196
220
 
197
221
  @property
198
- def name(self):
222
+ def name(self) -> str:
199
223
  return self.class_definition.class_name
200
224
 
201
225
  @property
202
- def ns(self):
226
+ def ns(self) -> Namespace:
203
227
  return self.namespace
204
228
 
205
229
  @property
206
- def cd(self):
230
+ def cd(self) -> ClassDefinition:
207
231
  return self.class_definition
208
232
 
209
233
  @property
210
- def derivation(self):
211
- """
212
- list from root to leaf of class layouts
213
- """
234
+ def derivation(self) -> list[Class]:
235
+ """List from root to leaf of class layouts."""
214
236
  derivation = []
215
237
 
216
238
  class_ = self
@@ -225,24 +247,22 @@ class Class:
225
247
  derivation.reverse()
226
248
  return derivation
227
249
 
228
- @property
229
- def properties(self):
230
- if self._properties is None:
231
- props = {}
232
- for class_ in self.derivation:
233
- for prop in class_.class_definition.properties.values():
234
- props[prop.name] = Property(self, prop)
235
- self._properties = props
236
- return self._properties
250
+ @cached_property
251
+ def properties(self) -> dict[str, Property]:
252
+ props = {}
253
+ for class_ in self.derivation:
254
+ for prop in class_.class_definition.properties.values():
255
+ props[prop.name] = Property(self, prop)
256
+ return props
237
257
 
238
258
  @property
239
- def property_default_values(self):
259
+ def property_default_values(self) -> PropertyDefaultValues:
240
260
  props = self.properties.values()
241
261
  props = sorted(props, key=lambda p: p.index)
242
262
  return PropertyDefaultValues(BytesIO(self.class_definition.default_values_data), props)
243
263
 
244
264
  @property
245
- def properties_length(self):
265
+ def properties_length(self) -> int:
246
266
  off = 0
247
267
  for prop in self.properties.values():
248
268
  if prop.type.array_state == ARRAY_STATES.ARRAY:
@@ -251,15 +271,15 @@ class Class:
251
271
  off += len(prop.ctype)
252
272
  return off
253
273
 
254
- def instance(self, key):
274
+ def instance(self, key: str) -> Instance:
255
275
  for instance in self.instances:
256
276
  if instance.key == key:
257
277
  return instance
258
278
 
259
- raise IndexError()
279
+ raise IndexError
260
280
 
261
281
  @property
262
- def instances(self):
282
+ def instances(self) -> Iterator[Instance]:
263
283
  yielded = set()
264
284
 
265
285
  q = self.cim.key().NS(self.namespace.name).CI(self.name).IL()
@@ -275,79 +295,83 @@ class Class:
275
295
 
276
296
 
277
297
  class Instance:
278
- def __init__(self, cim, namespace, class_, class_instance):
298
+ def __init__(self, cim: CIM, namespace: Namespace, class_: Class, class_instance: ClassInstance):
279
299
  self.cim = cim
280
300
  self.namespace = namespace
281
301
  self.class_ = class_
282
302
  self.class_definition = class_.class_definition
283
303
  self.class_instance = class_instance
284
304
 
285
- def __getattr__(self, attr):
305
+ def __getattr__(self, attr: str) -> Any:
286
306
  try:
287
307
  return getattr(self.class_instance, attr)
288
308
  except AttributeError:
289
309
  return object.__getattribute__(self, attr)
290
310
 
291
311
  @property
292
- def name(self):
312
+ def key(self) -> InstanceKey:
313
+ return self.class_instance.key
314
+
315
+ @property
316
+ def name(self) -> str:
293
317
  return self.class_instance.class_name
294
318
 
295
319
  @property
296
- def ns(self):
320
+ def ns(self) -> Namespace:
297
321
  return self.namespace
298
322
 
299
323
  @property
300
- def cd(self):
324
+ def cd(self) -> ClassDefinition:
301
325
  return self.class_definition
302
326
 
303
327
  @property
304
- def ci(self):
328
+ def ci(self) -> ClassInstance:
305
329
  return self.class_instance
306
330
 
307
331
 
308
332
  class Property:
309
- def __init__(self, class_, prop):
333
+ def __init__(self, class_: Class, prop: ClassDefinitionProperty):
310
334
  self.class_ = class_
311
335
  self._prop = prop
312
336
 
313
337
  @property
314
- def type(self):
338
+ def type(self) -> c_cim.cim_type:
315
339
  return self._prop.type
316
340
 
317
341
  @property
318
- def ctype(self):
342
+ def ctype(self) -> BaseType:
319
343
  return self._prop.ctype
320
344
 
321
345
  @property
322
- def qualifiers(self):
346
+ def qualifiers(self) -> dict[str, Qualifier]:
323
347
  return self._prop.qualifiers
324
348
 
325
349
  @property
326
- def name(self):
350
+ def name(self) -> str:
327
351
  return self._prop.name
328
352
 
329
353
  @property
330
- def index(self):
354
+ def index(self) -> int:
331
355
  return self._prop.index
332
356
 
333
357
  @property
334
- def offset(self):
358
+ def offset(self) -> int:
335
359
  return self._prop.offset
336
360
 
337
361
  @property
338
- def level(self):
362
+ def level(self) -> int:
339
363
  return self._prop.level
340
364
 
341
365
  @property
342
- def is_inherited(self):
366
+ def is_inherited(self) -> bool:
343
367
  return self.class_.property_default_values.state[self.index].is_inherited
344
368
 
345
369
  @property
346
- def has_default_value(self):
370
+ def has_default_value(self) -> bool:
347
371
  return self.class_.property_default_values.state[self.index].has_default_value
348
372
 
349
373
  @property
350
- def default_value(self):
374
+ def default_value(self) -> CimType | list[CimType]:
351
375
  if not self.has_default_value:
352
376
  raise ValueError("Property has no default value!")
353
377
 
@@ -355,22 +379,23 @@ class Property:
355
379
  # then the data is stored nicely in the CD prop data section
356
380
  v = self.class_.property_default_values.default_values[self.index]
357
381
  return self.class_.class_definition.property_data.get_value(v, self.type)
358
- else:
359
- # we have to walk up the derivation path looking for the default value
360
- rderivation = self.class_.derivation[:]
361
- rderivation.reverse()
362
-
363
- for ancestor_cl in rderivation:
364
- defaults = ancestor_cl.property_default_values
365
- state = defaults.state[self.index]
366
- if not state.has_default_value:
367
- raise Error("Property with inherited default value has bad ancestor (no default value)")
368
-
369
- if state.is_inherited:
370
- # keep trucking! look further up the ancestry tree.
371
- continue
372
-
373
- # else, this must be where the default value is defined
374
- v = defaults.default_values[self.index]
375
- return ancestor_cl.class_definition.property_data.get_value(v, self.type)
376
- raise Error("Unable to find ancestor class with default value")
382
+
383
+ # we have to walk up the derivation path looking for the default value
384
+ rderivation = self.class_.derivation[:]
385
+ rderivation.reverse()
386
+
387
+ for ancestor_cl in rderivation:
388
+ defaults = ancestor_cl.property_default_values
389
+ state = defaults.state[self.index]
390
+ if not state.has_default_value:
391
+ raise Error("Property with inherited default value has bad ancestor (no default value)")
392
+
393
+ if state.is_inherited:
394
+ # keep trucking! look further up the ancestry tree.
395
+ continue
396
+
397
+ # else, this must be where the default value is defined
398
+ v = defaults.default_values[self.index]
399
+ return ancestor_cl.class_definition.property_data.get_value(v, self.type)
400
+
401
+ raise Error("Unable to find ancestor class with default value")