dissect.thumbcache 1.9.dev3__tar.gz → 1.10.dev1__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 (86) hide show
  1. {dissect_thumbcache-1.9.dev3/dissect.thumbcache.egg-info → dissect_thumbcache-1.10.dev1}/PKG-INFO +2 -2
  2. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/dissect/thumbcache/c_thumbcache.py +4 -2
  3. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/dissect/thumbcache/exceptions.py +0 -8
  4. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/dissect/thumbcache/index.py +27 -26
  5. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/dissect/thumbcache/thumbcache.py +8 -4
  6. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/dissect/thumbcache/thumbcache_file.py +25 -24
  7. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/dissect/thumbcache/tools/extract_images.py +7 -3
  8. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/dissect/thumbcache/tools/extract_with_index.py +8 -3
  9. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/dissect/thumbcache/tools/utils.py +8 -4
  10. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/dissect/thumbcache/util.py +7 -2
  11. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1/dissect.thumbcache.egg-info}/PKG-INFO +2 -2
  12. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/dissect.thumbcache.egg-info/SOURCES.txt +1 -1
  13. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/pyproject.toml +48 -5
  14. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/test_index.py +9 -8
  15. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/test_thumbcache.py +9 -7
  16. dissect_thumbcache-1.9.dev3/tests/test_thumbcachefile.py → dissect_thumbcache-1.10.dev1/tests/test_thumbcache_file.py +18 -13
  17. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tox.ini +4 -17
  18. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/COPYRIGHT +0 -0
  19. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/LICENSE +0 -0
  20. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/MANIFEST.in +0 -0
  21. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/README.md +0 -0
  22. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/dissect/thumbcache/__init__.py +3 -3
  23. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/dissect/thumbcache/tools/__init__.py +0 -0
  24. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/dissect.thumbcache.egg-info/dependency_links.txt +0 -0
  25. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/dissect.thumbcache.egg-info/entry_points.txt +0 -0
  26. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/dissect.thumbcache.egg-info/requires.txt +0 -0
  27. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/dissect.thumbcache.egg-info/top_level.txt +0 -0
  28. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/setup.cfg +0 -0
  29. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/__init__.py +0 -0
  30. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_10/thumbcache_1280.db +0 -0
  31. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_10/thumbcache_16.db +0 -0
  32. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_10/thumbcache_1920.db +0 -0
  33. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_10/thumbcache_256.db +0 -0
  34. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_10/thumbcache_2560.db +0 -0
  35. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_10/thumbcache_32.db +0 -0
  36. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_10/thumbcache_48.db +0 -0
  37. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_10/thumbcache_768.db +0 -0
  38. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_10/thumbcache_96.db +0 -0
  39. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_10/thumbcache_custom_stream.db +0 -0
  40. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_10/thumbcache_exif.db +0 -0
  41. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_10/thumbcache_idx.db +0 -0
  42. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_10/thumbcache_sr.db +0 -0
  43. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_10/thumbcache_wide.db +0 -0
  44. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_10/thumbcache_wide_alternate.db +0 -0
  45. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_11/thumbcache_1280.db +0 -0
  46. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_11/thumbcache_16.db +0 -0
  47. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_11/thumbcache_1920.db +0 -0
  48. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_11/thumbcache_256.db +0 -0
  49. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_11/thumbcache_2560.db +0 -0
  50. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_11/thumbcache_32.db +0 -0
  51. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_11/thumbcache_48.db +0 -0
  52. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_11/thumbcache_768.db +0 -0
  53. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_11/thumbcache_96.db +0 -0
  54. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_11/thumbcache_custom_stream.db +0 -0
  55. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_11/thumbcache_exif.db +0 -0
  56. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_11/thumbcache_idx.db +0 -0
  57. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_11/thumbcache_sr.db +0 -0
  58. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_11/thumbcache_wide.db +0 -0
  59. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_11/thumbcache_wide_alternate.db +0 -0
  60. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_7/thumbcache_1024.db +0 -0
  61. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_7/thumbcache_256.db +0 -0
  62. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_7/thumbcache_32.db +0 -0
  63. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_7/thumbcache_96.db +0 -0
  64. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_7/thumbcache_idx.db +0 -0
  65. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_7/thumbcache_sr.db +0 -0
  66. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_81/thumbcache_1024.db +0 -0
  67. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_81/thumbcache_16.db +0 -0
  68. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_81/thumbcache_1600.db +0 -0
  69. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_81/thumbcache_256.db +0 -0
  70. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_81/thumbcache_32.db +0 -0
  71. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_81/thumbcache_48.db +0 -0
  72. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_81/thumbcache_96.db +0 -0
  73. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_81/thumbcache_exif.db +0 -0
  74. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_81/thumbcache_idx.db +0 -0
  75. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_81/thumbcache_sr.db +0 -0
  76. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_81/thumbcache_wide.db +0 -0
  77. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_81/thumbcache_wide_alternate.db +0 -0
  78. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_vista/thumbcache_1024.db +0 -0
  79. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_vista/thumbcache_256.db +0 -0
  80. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_vista/thumbcache_32.db +0 -0
  81. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_vista/thumbcache_96.db +0 -0
  82. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_vista/thumbcache_idx.db +0 -0
  83. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/data/windows_vista/thumbcache_sr.db +0 -0
  84. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/docs/Makefile +0 -0
  85. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/docs/conf.py +0 -0
  86. {dissect_thumbcache-1.9.dev3 → dissect_thumbcache-1.10.dev1}/tests/docs/index.rst +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: dissect.thumbcache
3
- Version: 1.9.dev3
3
+ Version: 1.10.dev1
4
4
  Summary: A Dissect module implementing parsers for the thumbcache of Windows systems.
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Affero General Public License v3
@@ -1,6 +1,8 @@
1
+ from __future__ import annotations
2
+
1
3
  from dissect.cstruct import cstruct
2
4
 
3
- thumbcache_index_def = """
5
+ thumbcache_def = """
4
6
  struct INDEX_HEADER_V1 {
5
7
  char Signature[4]; // 0x00
6
8
  uint32 Version; // 0x04
@@ -75,4 +77,4 @@ struct CACHE_ENTRY_VISTA {
75
77
  uint32 Unknown; // 0x28
76
78
  }; // 0x2C
77
79
  """
78
- c_thumbcache_index = cstruct().load(thumbcache_index_def)
80
+ c_thumbcache = cstruct().load(thumbcache_def)
@@ -1,22 +1,14 @@
1
1
  class Error(Exception):
2
2
  """A generic exception for the thumbcache module."""
3
3
 
4
- pass
5
-
6
4
 
7
5
  class NotAnIndexFileError(Error):
8
6
  """Raises if a thumbnail index signature could not be found."""
9
7
 
10
- pass
11
-
12
8
 
13
9
  class InvalidSignatureError(Error):
14
10
  """Raises if the signature does not match the expected value."""
15
11
 
16
- pass
17
-
18
12
 
19
13
  class UnknownThumbnailTypeError(Error):
20
14
  """Raises if an unknown thumbnail type was found."""
21
-
22
- pass
@@ -1,14 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
- from datetime import datetime
4
- from typing import BinaryIO, Iterator
3
+ from typing import TYPE_CHECKING, BinaryIO
5
4
 
6
5
  from dissect.util import ts
7
6
 
8
- from dissect.thumbcache.c_thumbcache import c_thumbcache_index
7
+ from dissect.thumbcache.c_thumbcache import c_thumbcache
9
8
  from dissect.thumbcache.exceptions import NotAnIndexFileError
10
9
  from dissect.thumbcache.util import ThumbnailType
11
10
 
11
+ if TYPE_CHECKING:
12
+ from collections.abc import Iterator
13
+ from datetime import datetime
14
+
12
15
  INDEX_ENTRIES = {
13
16
  ThumbnailType.WINDOWS_7: 5,
14
17
  ThumbnailType.WINDOWS_81: 11,
@@ -29,12 +32,12 @@ class ThumbnailIndex:
29
32
  self._header = None
30
33
 
31
34
  @property
32
- def header(self) -> c_thumbcache_index.INDEX_HEADER_V1 | c_thumbcache_index.INDEX_HEADER_V2:
35
+ def header(self) -> c_thumbcache.INDEX_HEADER_V1 | c_thumbcache.INDEX_HEADER_V2:
33
36
  if self._header is None:
34
37
  self._header = self._find_header(self.fh)
35
38
  return self._header
36
39
 
37
- def _find_header(self, fh: BinaryIO) -> c_thumbcache_index.INDEX_HEADER_V1 | c_thumbcache_index.INDEX_HEADER_V2:
40
+ def _find_header(self, fh: BinaryIO) -> c_thumbcache.INDEX_HEADER_V1 | c_thumbcache.INDEX_HEADER_V2:
38
41
  """Searches for the header signature, and puts ``fh`` at the correct position.
39
42
 
40
43
  From Windows 8.1 onward, the two fields seem to use a 64-bit format field
@@ -44,19 +47,19 @@ class ThumbnailIndex:
44
47
  fh: The file to read the header and indexes from.
45
48
 
46
49
  Returns:
47
- A c_thumbcache_index.INDEX_HEADER structure.
50
+ A c_thumbcache.INDEX_HEADER structure.
48
51
 
49
52
  Raises:
50
53
  NotAThumbnailIndexFileError: If the ``IMMM`` signature could not be found.
51
54
  """
52
55
  position = fh.tell()
53
- buffer = fh.read(len(c_thumbcache_index.INDEX_HEADER_V1))
56
+ buffer = fh.read(len(c_thumbcache.INDEX_HEADER_V1))
54
57
  offset = buffer.find(self._signature)
55
58
 
56
59
  if offset == MAX_IMM_OFFSET:
57
60
  fh.seek(position)
58
61
 
59
- header = c_thumbcache_index.INDEX_HEADER_V2(fh)
62
+ header = c_thumbcache.INDEX_HEADER_V2(fh)
60
63
  # From looking at the index files, it has a specific amount of information.
61
64
  # It is alligned in the follwing way:
62
65
  # INDEX_HEADER_V2
@@ -68,19 +71,18 @@ class ThumbnailIndex:
68
71
 
69
72
  # Read one index entry from the file till only zero bytes
70
73
  entry = IndexEntry(fh, header.Version)
71
- entry.header
72
- entry.cache_offsets
74
+ _ = entry.header
75
+ _ = entry.cache_offsets
73
76
 
74
77
  # Read offset to first entry
75
78
  zero_bytes = len(entry.header) + INDEX_ENTRIES.get(header.Version) * BYTES_IN_NUMBER - len(header)
76
79
  fh.read(zero_bytes)
77
80
  return header
78
- elif offset == 0:
79
- return c_thumbcache_index.INDEX_HEADER_V1(buffer)
80
- else:
81
- raise NotAnIndexFileError(
82
- f"The index file signature {self._signature!r} could not be found at the expected location."
83
- )
81
+ if offset == 0:
82
+ return c_thumbcache.INDEX_HEADER_V1(buffer)
83
+ raise NotAnIndexFileError(
84
+ f"The index file signature {self._signature!r} could not be found at the expected location."
85
+ )
84
86
 
85
87
  @property
86
88
  def version(self) -> int:
@@ -102,8 +104,8 @@ class ThumbnailIndex:
102
104
  """Returns all index entries that are actually used."""
103
105
  for _ in range(self.total_entries):
104
106
  entry = IndexEntry(self.fh, self.type)
105
- entry.header
106
- entry.cache_offsets
107
+ _ = entry.header
108
+ _ = entry.cache_offsets
107
109
 
108
110
  if entry.in_use():
109
111
  yield entry
@@ -119,21 +121,20 @@ class IndexEntry:
119
121
  @property
120
122
  def header(
121
123
  self,
122
- ) -> c_thumbcache_index.VISTA_ENTRY | c_thumbcache_index.WINDOWS7_ENTRY | c_thumbcache_index.WINDOWS8_ENTRY:
124
+ ) -> c_thumbcache.VISTA_ENTRY | c_thumbcache.WINDOWS7_ENTRY | c_thumbcache.WINDOWS8_ENTRY:
123
125
  if not self._header:
124
126
  self._header = self._select_header()
125
127
  return self._header
126
128
 
127
129
  def _select_header(
128
130
  self,
129
- ) -> c_thumbcache_index.VISTA_ENTRY | c_thumbcache_index.WINDOWS7_ENTRY | c_thumbcache_index.WINDOWS8_ENTRY:
131
+ ) -> c_thumbcache.VISTA_ENTRY | c_thumbcache.WINDOWS7_ENTRY | c_thumbcache.WINDOWS8_ENTRY:
130
132
  """Selects header version according to the thumbnailtype."""
131
133
  if self.type == ThumbnailType.WINDOWS_VISTA:
132
- return c_thumbcache_index.VISTA_ENTRY(self.fh)
133
- elif self.type == ThumbnailType.WINDOWS_7:
134
- return c_thumbcache_index.WINDOWS7_ENTRY(self.fh)
135
- else:
136
- return c_thumbcache_index.WINDOWS8_ENTRY(self.fh)
134
+ return c_thumbcache.VISTA_ENTRY(self.fh)
135
+ if self.type == ThumbnailType.WINDOWS_7:
136
+ return c_thumbcache.WINDOWS7_ENTRY(self.fh)
137
+ return c_thumbcache.WINDOWS8_ENTRY(self.fh)
137
138
 
138
139
  def in_use(self) -> bool:
139
140
  return self.identifier != b"\x00" * IDENTIFIER_BYTES
@@ -156,7 +157,7 @@ class IndexEntry:
156
157
  """
157
158
  if not self._data:
158
159
  size = INDEX_ENTRIES.get(self.type)
159
- self._data = c_thumbcache_index.uint32[size](self.fh)
160
+ self._data = c_thumbcache.uint32[size](self.fh)
160
161
  if self.type > ThumbnailType.WINDOWS_7:
161
162
  # Alignment step
162
163
  self.fh.read((size % 2) * BYTES_IN_NUMBER)
@@ -1,9 +1,14 @@
1
- from pathlib import Path
2
- from typing import Iterator
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
3
4
 
4
5
  from dissect.thumbcache.index import IndexEntry, ThumbnailIndex
5
6
  from dissect.thumbcache.thumbcache_file import ThumbcacheEntry, ThumbcacheFile
6
7
 
8
+ if TYPE_CHECKING:
9
+ from collections.abc import Iterator
10
+ from pathlib import Path
11
+
7
12
 
8
13
  class Thumbcache:
9
14
  """This class combines the thumbnailindex and thumbcachefile.
@@ -50,8 +55,7 @@ class Thumbcache:
50
55
  def index_entries(self) -> Iterator[IndexEntry]:
51
56
  """Iterates through all the index entries that are in use."""
52
57
  with self.index_file.open("rb") as i_file:
53
- for entry in ThumbnailIndex(i_file).entries():
54
- yield entry
58
+ yield from ThumbnailIndex(i_file).entries()
55
59
 
56
60
  def _entries_from_offsets(self, offsets: list[int]) -> Iterator[tuple[Path, ThumbcacheEntry]]:
57
61
  """Retrieves Thumbcache entries from a ThumbcacheFile using offsets."""
@@ -1,27 +1,30 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any, BinaryIO
3
+ from typing import TYPE_CHECKING, Any, BinaryIO
4
4
 
5
- from dissect.thumbcache.c_thumbcache import c_thumbcache_index
5
+ from dissect.thumbcache.c_thumbcache import c_thumbcache
6
6
  from dissect.thumbcache.exceptions import (
7
7
  InvalidSignatureError,
8
8
  UnknownThumbnailTypeError,
9
9
  )
10
10
  from dissect.thumbcache.util import ThumbnailType, seek_and_return
11
11
 
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Iterator
14
+
12
15
  UNKNOWN_BYTES = 8
13
16
 
14
17
 
15
18
  class ThumbcacheFile:
16
19
  __slots__ = [
17
- "fh",
20
+ "_cached_entries",
21
+ "_entries",
18
22
  "_header",
23
+ "fh",
24
+ "offset",
19
25
  "signature",
20
- "type",
21
26
  "size",
22
- "offset",
23
- "_entries",
24
- "_cached_entries",
27
+ "type",
25
28
  ]
26
29
 
27
30
  """
@@ -39,8 +42,8 @@ class ThumbcacheFile:
39
42
  self._header = self._get_header_type(self.fh)
40
43
  self._cached_entries: dict[int, ThumbcacheEntry] = {}
41
44
 
42
- def _get_header_type(self, fh: BinaryIO) -> c_thumbcache_index.CACHE_HEADER_VISTA | c_thumbcache_index.CACHE_HEADER:
43
- tmp_header = c_thumbcache_index.CACHE_HEADER(fh)
45
+ def _get_header_type(self, fh: BinaryIO) -> c_thumbcache.CACHE_HEADER_VISTA | c_thumbcache.CACHE_HEADER:
46
+ tmp_header = c_thumbcache.CACHE_HEADER(fh)
44
47
 
45
48
  if self._signature != tmp_header.Signature:
46
49
  raise InvalidSignatureError(
@@ -48,12 +51,11 @@ class ThumbcacheFile:
48
51
  )
49
52
 
50
53
  if tmp_header.Version <= ThumbnailType.WINDOWS_7:
51
- return c_thumbcache_index.CACHE_HEADER_VISTA(tmp_header.dumps())
52
- else:
53
- return tmp_header
54
+ return c_thumbcache.CACHE_HEADER_VISTA(tmp_header.dumps())
55
+ return tmp_header
54
56
 
55
57
  @property
56
- def header(self) -> c_thumbcache_index.CACHE_HEADER_VISTA | c_thumbcache_index.CACHE_HEADER:
58
+ def header(self) -> c_thumbcache.CACHE_HEADER_VISTA | c_thumbcache.CACHE_HEADER:
57
59
  return self._header
58
60
 
59
61
  @property
@@ -72,15 +74,15 @@ class ThumbcacheFile:
72
74
  self._cached_entries[key] = item
73
75
  return item
74
76
 
75
- def __getattribute__(self, __name: str) -> Any:
77
+ def __getattribute__(self, name: str) -> Any:
76
78
  try:
77
- return object.__getattribute__(self, __name)
79
+ return object.__getattribute__(self, name)
78
80
  except AttributeError:
79
81
  pass
80
82
 
81
- return getattr(self.header, __name.capitalize())
83
+ return getattr(self.header, name.capitalize())
82
84
 
83
- def entries(self) -> list[ThumbcacheEntry]:
85
+ def entries(self) -> Iterator[ThumbcacheEntry]:
84
86
  with seek_and_return(self.fh, self.fh.tell()):
85
87
  try:
86
88
  while True:
@@ -123,10 +125,10 @@ class ThumbcacheEntry:
123
125
  fh.read(UNKNOWN_BYTES)
124
126
  additional_bytes += UNKNOWN_BYTES
125
127
 
126
- self.data_checksum: bytes = c_thumbcache_index.char[8](fh)
127
- self.header_checksum: bytes = c_thumbcache_index.char[8](fh)
128
+ self.data_checksum: bytes = c_thumbcache.char[8](fh)
129
+ self.header_checksum: bytes = c_thumbcache.char[8](fh)
128
130
 
129
- self.identifier: str = c_thumbcache_index.wchar[self._header.IdentifierSize // 2](fh)
131
+ self.identifier: str = c_thumbcache.wchar[self._header.IdentifierSize // 2](fh)
130
132
 
131
133
  header_size = len(self._header) + self._header.IdentifierSize + additional_bytes
132
134
 
@@ -134,11 +136,10 @@ class ThumbcacheEntry:
134
136
 
135
137
  def _get_header(
136
138
  self, thumbnail_type: ThumbnailType
137
- ) -> type[c_thumbcache_index.CACHE_ENTRY_VISTA | c_thumbcache_index.CACHE_ENTRY]:
139
+ ) -> type[c_thumbcache.CACHE_ENTRY_VISTA | c_thumbcache.CACHE_ENTRY]:
138
140
  if thumbnail_type == ThumbnailType.WINDOWS_VISTA:
139
- return c_thumbcache_index.CACHE_ENTRY_VISTA
140
- else:
141
- return c_thumbcache_index.CACHE_ENTRY
141
+ return c_thumbcache.CACHE_ENTRY_VISTA
142
+ return c_thumbcache.CACHE_ENTRY
142
143
 
143
144
  @property
144
145
  def hash(self) -> str:
@@ -1,13 +1,17 @@
1
1
  #!/bin/python3
2
+ from __future__ import annotations
2
3
 
3
- from pathlib import Path
4
+ from typing import TYPE_CHECKING
4
5
 
5
6
  from dissect.thumbcache.exceptions import Error
6
7
  from dissect.thumbcache.thumbcache_file import ThumbcacheFile
7
8
  from dissect.thumbcache.tools.utils import create_argument_parser, write_entry
8
9
 
10
+ if TYPE_CHECKING:
11
+ from pathlib import Path
9
12
 
10
- def dump_entry_data(path: Path, output_dir: Path):
13
+
14
+ def dump_entry_data(path: Path, output_dir: Path) -> None:
11
15
  with path.open("rb") as file:
12
16
  try:
13
17
  cache_file = ThumbcacheFile(file)
@@ -18,7 +22,7 @@ def dump_entry_data(path: Path, output_dir: Path):
18
22
  print(e)
19
23
 
20
24
 
21
- def main():
25
+ def main() -> None:
22
26
  parser = create_argument_parser("extract raw thumbcache entries")
23
27
  args = parser.parse_args()
24
28
  path: Path = args.cache_path
@@ -1,11 +1,16 @@
1
- from pathlib import Path
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
2
4
 
3
5
  from dissect.thumbcache.exceptions import Error
4
6
  from dissect.thumbcache.thumbcache import Thumbcache
5
7
  from dissect.thumbcache.tools.utils import create_argument_parser, write_entry
6
8
 
9
+ if TYPE_CHECKING:
10
+ from pathlib import Path
11
+
7
12
 
8
- def dump_entry_data_through_index(path: Path, output_dir: Path, prefix: str):
13
+ def dump_entry_data_through_index(path: Path, output_dir: Path, prefix: str) -> None:
9
14
  cache = Thumbcache(path=path, prefix=prefix)
10
15
  try:
11
16
  for location_path, entry in cache.entries():
@@ -15,7 +20,7 @@ def dump_entry_data_through_index(path: Path, output_dir: Path, prefix: str):
15
20
  print(e)
16
21
 
17
22
 
18
- def main():
23
+ def main() -> None:
19
24
  parser = create_argument_parser("extract indexed entries")
20
25
  parser.add_argument(
21
26
  "--prefix",
@@ -1,18 +1,22 @@
1
+ from __future__ import annotations
2
+
1
3
  from argparse import ArgumentParser
2
4
  from pathlib import Path
5
+ from typing import TYPE_CHECKING
3
6
 
4
- from dissect.thumbcache.thumbcache import ThumbcacheEntry
7
+ if TYPE_CHECKING:
8
+ from dissect.thumbcache.thumbcache import ThumbcacheEntry
5
9
 
6
10
 
7
- def create_argument_parser(name: str) -> ArgumentParser():
11
+ def create_argument_parser(name: str) -> ArgumentParser:
8
12
  parser = ArgumentParser(name)
9
13
  parser.add_argument("cache_path", type=Path)
10
- parser.add_argument("--output-dir", "-o", type=Path, default=Path("."))
14
+ parser.add_argument("--output-dir", "-o", type=Path, default=Path())
11
15
 
12
16
  return parser
13
17
 
14
18
 
15
- def write_entry(output_path: Path, entry: ThumbcacheEntry, file_prefix: str):
19
+ def write_entry(output_path: Path, entry: ThumbcacheEntry, file_prefix: str) -> None:
16
20
  output_file = (output_path / f"{entry.hash}/{entry.identifier}_{file_prefix}").with_suffix(".bmp")
17
21
  output_file.parent.mkdir(parents=True, exist_ok=True)
18
22
  with output_file.open("wb") as output:
@@ -1,6 +1,11 @@
1
+ from __future__ import annotations
2
+
1
3
  import enum
2
4
  from contextlib import contextmanager
3
- from typing import BinaryIO
5
+ from typing import TYPE_CHECKING, BinaryIO
6
+
7
+ if TYPE_CHECKING:
8
+ from collections.abc import Iterator
4
9
 
5
10
 
6
11
  class ThumbnailType(enum.IntEnum):
@@ -11,7 +16,7 @@ class ThumbnailType(enum.IntEnum):
11
16
 
12
17
 
13
18
  @contextmanager
14
- def seek_and_return(fh: BinaryIO, position: int) -> BinaryIO:
19
+ def seek_and_return(fh: BinaryIO, position: int) -> Iterator[BinaryIO]:
15
20
  current_position = fh.tell()
16
21
  try:
17
22
  fh.seek(position)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: dissect.thumbcache
3
- Version: 1.9.dev3
3
+ Version: 1.10.dev1
4
4
  Summary: A Dissect module implementing parsers for the thumbcache of Windows systems.
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Affero General Public License v3
@@ -24,7 +24,7 @@ dissect/thumbcache/tools/utils.py
24
24
  tests/__init__.py
25
25
  tests/test_index.py
26
26
  tests/test_thumbcache.py
27
- tests/test_thumbcachefile.py
27
+ tests/test_thumbcache_file.py
28
28
  tests/data/windows_10/thumbcache_1280.db
29
29
  tests/data/windows_10/thumbcache_16.db
30
30
  tests/data/windows_10/thumbcache_1920.db
@@ -45,13 +45,56 @@ dev = [
45
45
  thumbcache-extract = "dissect.thumbcache.tools.extract_images:main"
46
46
  thumbcache-extract-indexed = "dissect.thumbcache.tools.extract_with_index:main"
47
47
 
48
- [tool.black]
48
+ [tool.ruff]
49
49
  line-length = 120
50
+ required-version = ">=0.9.0"
50
51
 
51
- [tool.isort]
52
- profile = "black"
53
- known_first_party = ["dissect.thumbcache"]
54
- known_third_party = ["dissect"]
52
+ [tool.ruff.format]
53
+ docstring-code-format = true
54
+
55
+ [tool.ruff.lint]
56
+ select = [
57
+ "F",
58
+ "E",
59
+ "W",
60
+ "I",
61
+ "UP",
62
+ "YTT",
63
+ "ANN",
64
+ "B",
65
+ "C4",
66
+ "DTZ",
67
+ "T10",
68
+ "FA",
69
+ "ISC",
70
+ "G",
71
+ "INP",
72
+ "PIE",
73
+ "PYI",
74
+ "PT",
75
+ "Q",
76
+ "RSE",
77
+ "RET",
78
+ "SLOT",
79
+ "SIM",
80
+ "TID",
81
+ "TCH",
82
+ "PTH",
83
+ "PLC",
84
+ "TRY",
85
+ "FLY",
86
+ "PERF",
87
+ "FURB",
88
+ "RUF",
89
+ ]
90
+ ignore = ["E203", "B904", "UP024", "ANN002", "ANN003", "ANN204", "ANN401", "SIM105", "TRY003"]
91
+
92
+ [tool.ruff.lint.per-file-ignores]
93
+ "tests/docs/**" = ["INP001"]
94
+
95
+ [tool.ruff.lint.isort]
96
+ known-first-party = ["dissect.thumbcache"]
97
+ known-third-party = ["dissect"]
55
98
 
56
99
  [tool.setuptools]
57
100
  license-files = ["LICENSE", "COPYRIGHT"]
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import io
2
4
  from pathlib import Path
3
5
  from unittest.mock import Mock
@@ -8,12 +10,12 @@ from dissect.thumbcache.exceptions import NotAnIndexFileError
8
10
  from dissect.thumbcache.index import IndexEntry, ThumbnailIndex, ThumbnailType
9
11
 
10
12
 
11
- def test_index():
13
+ def test_index() -> None:
12
14
  ThumbnailIndex(fh=Mock)
13
15
 
14
16
 
15
17
  @pytest.mark.parametrize(
16
- "path, expected_type",
18
+ ("path", "expected_type"),
17
19
  [
18
20
  (Path("data/windows_vista/thumbcache_idx.db"), ThumbnailType.WINDOWS_VISTA),
19
21
  (Path("data/windows_7/thumbcache_idx.db"), ThumbnailType.WINDOWS_7),
@@ -25,7 +27,7 @@ def test_index():
25
27
  def test_index_type(
26
28
  path: Path,
27
29
  expected_type: ThumbnailType,
28
- ):
30
+ ) -> None:
29
31
  index_path = Path(__file__).parent / path
30
32
 
31
33
  with index_path.open("rb") as ifile:
@@ -33,13 +35,12 @@ def test_index_type(
33
35
  assert index.type == expected_type
34
36
 
35
37
 
36
- def test_index_unknown():
38
+ def test_index_unknown() -> None:
37
39
  with pytest.raises(NotAnIndexFileError):
38
- index = ThumbnailIndex(io.BytesIO(b"UNKNOWN_DATA"))
39
- index.header
40
+ _ = ThumbnailIndex(io.BytesIO(b"UNKNOWN_DATA")).header
40
41
 
41
42
 
42
- def test_index_entry():
43
+ def test_index_entry() -> None:
43
44
  index_entry = IndexEntry(io.BytesIO(b"\x00" * 20), ThumbnailType.WINDOWS_VISTA)
44
45
  assert not index_entry.in_use()
45
46
 
@@ -54,7 +55,7 @@ def test_index_entry():
54
55
  "data/windows_vista/thumbcache_idx.db",
55
56
  ],
56
57
  )
57
- def test_get_index_entries(path: str):
58
+ def test_get_index_entries(path: str) -> None:
58
59
  idx_file = Path(__file__).parent / path
59
60
  with idx_file.open("rb") as index_file:
60
61
  index = ThumbnailIndex(index_file)
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from pathlib import Path
2
4
 
3
5
  import pytest
@@ -11,12 +13,12 @@ def thumbcache(path: str) -> Thumbcache:
11
13
  return Thumbcache(file)
12
14
 
13
15
 
14
- def test_thumbcache():
16
+ def test_thumbcache() -> None:
15
17
  assert Thumbcache(Path(__file__).parent / "data/windows_7")
16
18
 
17
19
 
18
20
  @pytest.mark.parametrize(
19
- "path, expected_mapping",
21
+ ("path", "expected_mapping"),
20
22
  [
21
23
  ("data/windows_vista", {0: "32", 1: "96", 2: "256", 3: "1024", 4: "sr"}),
22
24
  (
@@ -60,13 +62,13 @@ def test_thumbcache():
60
62
  ),
61
63
  ],
62
64
  )
63
- def test_thumbcache_mapping(thumbcache: Thumbcache, expected_mapping: dict):
65
+ def test_thumbcache_mapping(thumbcache: Thumbcache, expected_mapping: dict) -> None:
64
66
  for key, value in expected_mapping.items():
65
67
  assert value in thumbcache.mapping[key].name
66
68
 
67
69
 
68
70
  @pytest.mark.parametrize(
69
- "path, nr_of_entries",
71
+ ("path", "nr_of_entries"),
70
72
  [
71
73
  (
72
74
  "data/windows_7",
@@ -78,16 +80,16 @@ def test_thumbcache_mapping(thumbcache: Thumbcache, expected_mapping: dict):
78
80
  ),
79
81
  ],
80
82
  )
81
- def test_thumbcache_entries(thumbcache: Thumbcache, nr_of_entries: int):
83
+ def test_thumbcache_entries(thumbcache: Thumbcache, nr_of_entries: int) -> None:
82
84
  assert len(list(thumbcache.entries())) == nr_of_entries
83
85
 
84
86
 
85
87
  @pytest.mark.parametrize(
86
- "path, offsets, expected_entries",
88
+ ("path", "offsets", "expected_entries"),
87
89
  [
88
90
  ("data/windows_7", [0xFFFFFFFF, 0xFFFFFFFF, 152, 0xFFFFFFFF, 0xFFFFFFFF], 1),
89
91
  ("data/windows_7", [0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF], 0),
90
92
  ],
91
93
  )
92
- def test_entries_from_offset(thumbcache: Thumbcache, offsets, expected_entries):
94
+ def test_entries_from_offset(thumbcache: Thumbcache, offsets: list[int], expected_entries: int) -> None:
93
95
  assert len(list(thumbcache._entries_from_offsets(offsets))) == expected_entries
@@ -1,8 +1,11 @@
1
+ from __future__ import annotations
2
+
1
3
  from pathlib import Path
4
+ from typing import TYPE_CHECKING
2
5
 
3
6
  import pytest
4
7
 
5
- from dissect.thumbcache.c_thumbcache import c_thumbcache_index
8
+ from dissect.thumbcache.c_thumbcache import c_thumbcache
6
9
  from dissect.thumbcache.exceptions import (
7
10
  InvalidSignatureError,
8
11
  UnknownThumbnailTypeError,
@@ -10,9 +13,12 @@ from dissect.thumbcache.exceptions import (
10
13
  from dissect.thumbcache.thumbcache_file import ThumbcacheFile
11
14
  from dissect.thumbcache.util import ThumbnailType
12
15
 
16
+ if TYPE_CHECKING:
17
+ from collections.abc import Iterator
18
+
13
19
 
14
20
  @pytest.fixture
15
- def thumbcache_file(path: str) -> ThumbcacheFile:
21
+ def thumbcache_file(path: str) -> Iterator[ThumbcacheFile]:
16
22
  current_path = Path(__file__).parent / "data" / path
17
23
 
18
24
  with current_path.open("rb") as thumb_file:
@@ -20,7 +26,7 @@ def thumbcache_file(path: str) -> ThumbcacheFile:
20
26
 
21
27
 
22
28
  @pytest.mark.parametrize(
23
- "path, thumbnail_type",
29
+ ("path", "thumbnail_type"),
24
30
  [
25
31
  ("windows_7/thumbcache_32.db", ThumbnailType.WINDOWS_7),
26
32
  ("windows_10/thumbcache_32.db", ThumbnailType.WINDOWS_10),
@@ -28,25 +34,24 @@ def thumbcache_file(path: str) -> ThumbcacheFile:
28
34
  ("windows_81/thumbcache_32.db", ThumbnailType.WINDOWS_81),
29
35
  ],
30
36
  )
31
- def test_thumbcache_version(thumbcache_file: ThumbcacheFile, thumbnail_type: ThumbnailType):
37
+ def test_thumbcache_version(thumbcache_file: ThumbcacheFile, thumbnail_type: ThumbnailType) -> None:
32
38
  assert thumbcache_file.version == thumbnail_type
33
39
  assert thumbcache_file.signature == b"CMMM"
34
40
 
35
41
 
36
- def test_thumbcache_file_failed():
42
+ def test_thumbcache_file_failed() -> None:
37
43
  with pytest.raises(InvalidSignatureError):
38
44
  ThumbcacheFile(b"UNKNOWN" + b"\x00" * 20)
39
45
 
40
46
 
41
- def test_unknown_thumbnail_type():
47
+ def test_unknown_thumbnail_type() -> None:
48
+ header = c_thumbcache.CACHE_HEADER(Signature=b"CMMM", Version=0xDEADBEEF)
42
49
  with pytest.raises(UnknownThumbnailTypeError):
43
- header = c_thumbcache_index.CACHE_HEADER(Signature=b"CMMM", Version=0xDEADBEEF)
44
- cache_file = ThumbcacheFile(header.dumps())
45
- cache_file.version
50
+ _ = ThumbcacheFile(header.dumps()).version
46
51
 
47
52
 
48
53
  @pytest.mark.parametrize(
49
- "path, entries, size",
54
+ ("path", "entries", "size"),
50
55
  [
51
56
  ("windows_7/thumbcache_256.db", 0x3, 0x0118),
52
57
  ("windows_10/thumbcache_32.db", 0x6, 0),
@@ -55,7 +60,7 @@ def test_unknown_thumbnail_type():
55
60
  ("windows_vista/thumbcache_32.db", 0x12, 0xDDA8),
56
61
  ],
57
62
  )
58
- def test_thumbcache_entries(thumbcache_file: ThumbcacheFile, entries: int, size: int):
63
+ def test_thumbcache_entries(thumbcache_file: ThumbcacheFile, entries: int, size: int) -> None:
59
64
  assert len(list(thumbcache_file.entries())) == entries
60
65
  assert thumbcache_file.size == size
61
66
 
@@ -64,7 +69,7 @@ def test_thumbcache_entries(thumbcache_file: ThumbcacheFile, entries: int, size:
64
69
  "path",
65
70
  [("windows_7/thumbcache_256.db")],
66
71
  )
67
- def test_thumbcache_get_entry(thumbcache_file: ThumbcacheFile):
72
+ def test_thumbcache_get_entry(thumbcache_file: ThumbcacheFile) -> None:
68
73
  assert thumbcache_file[0x18].hash == "e84eb8f951bc2409"
69
74
 
70
75
 
@@ -72,6 +77,6 @@ def test_thumbcache_get_entry(thumbcache_file: ThumbcacheFile):
72
77
  "path",
73
78
  [("windows_7/thumbcache_256.db")],
74
79
  )
75
- def test_thumbcache_get_entry_failed(thumbcache_file: ThumbcacheFile):
80
+ def test_thumbcache_get_entry_failed(thumbcache_file: ThumbcacheFile) -> None:
76
81
  with pytest.raises(InvalidSignatureError):
77
82
  thumbcache_file[0x42]
@@ -32,32 +32,19 @@ commands =
32
32
  [testenv:fix]
33
33
  package = skip
34
34
  deps =
35
- black==23.1.0
36
- isort==5.11.4
35
+ ruff==0.9.2
37
36
  commands =
38
- black dissect tests
39
- isort dissect tests
37
+ ruff format dissect tests
40
38
 
41
39
  [testenv:lint]
42
40
  package = skip
43
41
  deps =
44
- black==23.1.0
45
- flake8
46
- flake8-black
47
- flake8-isort
48
- isort==5.11.4
42
+ ruff==0.9.2
49
43
  vermin
50
44
  commands =
51
- flake8 dissect tests
45
+ ruff check dissect tests
52
46
  vermin -t=3.9- --no-tips --lint dissect tests
53
47
 
54
- [flake8]
55
- max-line-length = 120
56
- extend-ignore =
57
- # See https://github.com/PyCQA/pycodestyle/issues/373
58
- E203,
59
- statistics = True
60
-
61
48
  [testenv:docs-build]
62
49
  allowlist_externals = make
63
50
  deps =
@@ -6,8 +6,8 @@ from dissect.thumbcache.thumbcache_file import ThumbcacheEntry, ThumbcacheFile
6
6
  __all__ = [
7
7
  "Error",
8
8
  "IndexEntry",
9
- "ThumbnailIndex",
10
- "ThumbcacheFile",
11
- "ThumbcacheEntry",
12
9
  "Thumbcache",
10
+ "ThumbcacheEntry",
11
+ "ThumbcacheFile",
12
+ "ThumbnailIndex",
13
13
  ]