dfindexeddb 20251109__py3-none-any.whl → 20260205__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.
@@ -54,6 +54,8 @@ class IndexedDBRecord:
54
54
  object_store_name: the object store name from the ObjectStoreInfo table.
55
55
  database_name: the IndexedDB database name from the IDBDatabaseInfo table.
56
56
  record_id: the record ID from the Record table.
57
+ raw_key: the raw key.
58
+ raw_value: the raw value.
57
59
  """
58
60
 
59
61
  key: Any
@@ -62,6 +64,8 @@ class IndexedDBRecord:
62
64
  object_store_name: str
63
65
  database_name: str
64
66
  record_id: int
67
+ raw_key: Optional[bytes] = None
68
+ raw_value: Optional[bytes] = None
65
69
 
66
70
 
67
71
  class FileReader:
@@ -128,7 +132,9 @@ class FileReader:
128
132
  database_name=self.database_name,
129
133
  )
130
134
 
131
- def RecordById(self, record_id: int) -> Optional[IndexedDBRecord]:
135
+ def RecordById(
136
+ self, record_id: int, include_raw_data: bool = False
137
+ ) -> Optional[IndexedDBRecord]:
132
138
  """Returns an IndexedDBRecord for the given record_id.
133
139
 
134
140
  Returns:
@@ -156,10 +162,12 @@ class FileReader:
156
162
  object_store_name=row[3].decode("utf-8"),
157
163
  database_name=self.database_name,
158
164
  record_id=row[4],
165
+ raw_key=row[0] if include_raw_data else None,
166
+ raw_value=row[1] if include_raw_data else None,
159
167
  )
160
168
 
161
169
  def RecordsByObjectStoreName(
162
- self, name: str
170
+ self, name: str, include_raw_data: bool = False
163
171
  ) -> Generator[IndexedDBRecord, None, None]:
164
172
  """Returns IndexedDBRecords for the given ObjectStore name.
165
173
 
@@ -184,10 +192,12 @@ class FileReader:
184
192
  object_store_name=row[3].decode("utf-8"),
185
193
  database_name=self.database_name,
186
194
  record_id=row[4],
195
+ raw_key=row[0] if include_raw_data else None,
196
+ raw_value=row[1] if include_raw_data else None,
187
197
  )
188
198
 
189
199
  def RecordsByObjectStoreId(
190
- self, object_store_id: int
200
+ self, object_store_id: int, include_raw_data: bool = False
191
201
  ) -> Generator[IndexedDBRecord, None, None]:
192
202
  """Returns IndexedDBRecords for the given ObjectStore id.
193
203
 
@@ -213,9 +223,13 @@ class FileReader:
213
223
  object_store_name=row[3].decode("utf-8"),
214
224
  database_name=self.database_name,
215
225
  record_id=row[4],
226
+ raw_key=row[0] if include_raw_data else None,
227
+ raw_value=row[1] if include_raw_data else None,
216
228
  )
217
229
 
218
- def Records(self) -> Generator[IndexedDBRecord, None, None]:
230
+ def Records(
231
+ self, include_raw_data: bool = False
232
+ ) -> Generator[IndexedDBRecord, None, None]:
219
233
  """Returns all the IndexedDBRecords."""
220
234
  with sqlite3.connect(f"file:{self.filename}?mode=ro", uri=True) as conn:
221
235
  conn.text_factory = bytes
@@ -252,4 +266,6 @@ class FileReader:
252
266
  object_store_name=row[3].decode("utf-8"),
253
267
  database_name=self.database_name,
254
268
  record_id=row[4],
269
+ raw_key=row[0] if include_raw_data else None,
270
+ raw_value=row[1] if include_raw_data else None,
255
271
  )
@@ -16,11 +16,21 @@
16
16
  from __future__ import annotations
17
17
 
18
18
  import io
19
+ import struct
19
20
  from typing import BinaryIO, Tuple, Type, TypeVar
20
21
 
21
22
  from dfindexeddb import errors, utils
22
23
 
23
24
 
25
+ _CHUNK_SIZE = 8
26
+ _MARKER = _CHUNK_SIZE + 1
27
+ _EMPTY_BINARY_SENTINEL = 0
28
+ _SIGN_BIT = 1 << 63
29
+ _SENTINEL = 0
30
+ _TWO_BYTE_ENCODING_INDICATOR = 0x80
31
+ _THREE_BYTE_ENCODING_INDICATOR = 0xFF
32
+
33
+
24
34
  class LevelDBDecoder(utils.StreamDecoder):
25
35
  """A helper class to decode data types from LevelDB files."""
26
36
 
@@ -64,6 +74,80 @@ class LevelDBDecoder(utils.StreamDecoder):
64
74
  _, buffer = self.ReadBytes(length * 2)
65
75
  return offset, buffer.decode(encoding=encoding)
66
76
 
77
+ def DecodeSortableBinary(self) -> Tuple[int, bytes]:
78
+ """Decodes a sortable binary from the binary stream.
79
+
80
+ Returns:
81
+ a tuple of the offset and the decoded bytes.
82
+
83
+ Raises:
84
+ errors.ParserError: if an invalid tag is encountered.
85
+ """
86
+ output = bytearray()
87
+
88
+ offset, first = self.PeekBytes(1)
89
+ if first[0] == _EMPTY_BINARY_SENTINEL:
90
+ self.ReadBytes(1)
91
+ return offset, b""
92
+
93
+ while True:
94
+ _, marker_or_sentinel = self.DecodeUint8()
95
+ if marker_or_sentinel == _MARKER:
96
+ _, chunk = self.ReadBytes(_CHUNK_SIZE)
97
+ output.extend(chunk)
98
+ continue
99
+ if 1 <= marker_or_sentinel <= _CHUNK_SIZE:
100
+ payload_len = marker_or_sentinel
101
+ padding_len = _CHUNK_SIZE - payload_len
102
+ if padding_len > 0:
103
+ return offset, bytes(output[:-padding_len])
104
+ return offset, bytes(output)
105
+ raise errors.ParserError(
106
+ f"Invalid marker or sentinel {marker_or_sentinel} in sortable binary"
107
+ )
108
+
109
+ def DecodeSortableDouble(self) -> Tuple[int, float]:
110
+ """Decodes a sortable double-precision float from the binary stream.
111
+
112
+ Returns:
113
+ a tuple of the offset and the decoded double.
114
+ """
115
+ offset, host_bits = self.DecodeInt(8, byte_order="big", signed=False)
116
+ if host_bits & _SIGN_BIT:
117
+ host_bits ^= _SIGN_BIT
118
+ else:
119
+ host_bits ^= 0xFFFFFFFFFFFFFFFF
120
+ blob = host_bits.to_bytes(8, byteorder="big")
121
+ return offset, struct.unpack(">d", blob)[0]
122
+
123
+ def DecodeSortableString(self) -> Tuple[int, str]:
124
+ """Decodes a sortable string from the binary stream.
125
+
126
+ Returns:
127
+ a tuple of the offset and the decoded string.
128
+
129
+ Raises:
130
+ errors.ParserError: if an invalid byte is encountered.
131
+ """
132
+ output = []
133
+ offset = self.stream.tell()
134
+ while True:
135
+ _, first = self.DecodeUint8()
136
+ if first == _SENTINEL:
137
+ break
138
+ if (first & 0x80) == 0:
139
+ output.append(chr((first & 0x7F) - 1))
140
+ elif (first & 0xC0) == _TWO_BYTE_ENCODING_INDICATOR:
141
+ _, second = self.DecodeUint8()
142
+ output.append(chr(((first & 0x3F) << 8) | second))
143
+ elif first == _THREE_BYTE_ENCODING_INDICATOR:
144
+ _, high = self.DecodeUint8()
145
+ _, low = self.DecodeUint8()
146
+ output.append(chr((high << 8) | low))
147
+ else:
148
+ raise errors.ParserError(f"Invalid byte {first} in sortable string")
149
+ return offset, "".join(output)
150
+
67
151
 
68
152
  T = TypeVar("T")
69
153
 
dfindexeddb/version.py CHANGED
@@ -15,7 +15,7 @@
15
15
  """Version information for dfIndexeddb."""
16
16
 
17
17
 
18
- __version__ = "20251109"
18
+ __version__ = "20260205"
19
19
 
20
20
 
21
21
  def GetVersion() -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dfindexeddb
3
- Version: 20251109
3
+ Version: 20260205
4
4
  Summary: dfindexeddb is an experimental Python tool for performing digital forensic analysis of IndexedDB and leveldb files.
5
5
  Author-email: Syd Pleno <sydp@google.com>
6
6
  Maintainer-email: dfIndexeddb Developers <dfindexeddb-dev@googlegroups.com>
@@ -55,6 +55,8 @@ include:
55
55
  $ pip install dfindexeddb
56
56
  ```
57
57
 
58
+ ### Optional plugins
59
+
58
60
  To also install the dependencies for leveldb/indexeddb plugins, run
59
61
  ```
60
62
  $ pip install 'dfindexeddb[plugins]'
@@ -79,6 +81,8 @@ To also install the dependencies for leveldb/indexeddb plugins, run
79
81
  $ pip install .
80
82
  ```
81
83
 
84
+ ### Optional plugins
85
+
82
86
  To also install the dependencies for leveldb/indexeddb plugins, run
83
87
  ```
84
88
  $ pip install '.[plugins]'
@@ -94,15 +98,17 @@ installation:
94
98
 
95
99
  ```
96
100
  $ dfindexeddb -h
97
- usage: dfindexeddb [-h] {db,ldb,log} ...
101
+ usage: dfindexeddb [-h] {blink,gecko,db,ldb,log} ...
98
102
 
99
- A cli tool for parsing indexeddb files
103
+ A cli tool for parsing IndexedDB files
100
104
 
101
105
  positional arguments:
102
- {db,ldb,log}
103
- db Parse a directory as indexeddb.
104
- ldb Parse a ldb file as indexeddb.
105
- log Parse a log file as indexeddb.
106
+ {blink,gecko,db,ldb,log}
107
+ blink Parse a file as a blink-encoded value.
108
+ gecko Parse a file as a gecko-encoded value.
109
+ db Parse a directory/file as IndexedDB.
110
+ ldb Parse a ldb file as IndexedDB.
111
+ log Parse a log file as IndexedDB.
106
112
 
107
113
  options:
108
114
  -h, --help show this help message and exit
@@ -110,48 +116,17 @@ options:
110
116
 
111
117
  #### Examples:
112
118
 
113
- To parse IndexedDB records from an sqlite file for Firefox and output the
114
- results as JSON, use the following command:
115
-
116
- ```
117
- dfindexeddb db -s SOURCE --format firefox -o json
118
- ```
119
-
120
- To parse IndexedDB records from an sqlite file for Safari and output the
121
- results as JSON-L, use the following command:
122
-
123
- ```
124
- dfindexeddb db -s SOURCE --format safari -o jsonl
125
- ```
126
-
127
- To parse IndexedDB records from a LevelDB folder for Chrome/Chromium, using the
128
- manifest file to determine recovered records and output as JSON, use the
129
- following command:
130
-
131
- ```
132
- dfindexeddb db -s SOURCE --format chrome --use_manifest
133
- ```
134
-
135
- To parse IndexedDB records from a LevelDB ldb (.ldb) file and output the
136
- results as JSON-L, use the following command:
137
-
138
- ```
139
- dfindexeddb ldb -s SOURCE -o jsonl
140
- ```
141
-
142
- To parse IndexedDB records from a LevelDB log (.log) file and output the
143
- results as the Python printable representation, use the following command:
144
-
145
- ```
146
- dfindexeddb log -s SOURCE -o repr
147
- ```
148
-
149
- To parse a file as a Chrome/Chromium IndexedDB blink value and output the
150
- results as JSON:
119
+ | Platform / Source | Format | Command |
120
+ | :--- | :--- | :--- |
121
+ | **Firefox** (sqlite) | JSON | `dfindexeddb db -s SOURCE --format firefox -o json` |
122
+ | **Safari** (sqlite) | JSON-L | `dfindexeddb db -s SOURCE --format safari -o jsonl` |
123
+ | **Chrome** (LevelDB/sqlite) | JSON | `dfindexeddb db -s SOURCE --format chrome` |
124
+ | **Chrome** (.ldb) | JSON-L | `dfindexeddb ldb -s SOURCE -o jsonl` |
125
+ | **Chrome** (.log) | Python repr | `dfindexeddb log -s SOURCE -o repr` |
126
+ | **Chrome** (Blink) | JSON | `dfindexeddb blink -s SOURCE` |
127
+ | **Filter Records by key** | JSON | `dfindexeddb db -s SOURCE --format chrome --filter_key search_term` |
128
+ | **Filter Records by value** | JSON | `dfindexeddb db -s SOURCE --format chrome --filter_value "search_term"` |
151
129
 
152
- ```
153
- dfindexeddb blink -s SOURCE
154
- ```
155
130
 
156
131
  ### LevelDB
157
132
 
@@ -174,44 +149,18 @@ options:
174
149
 
175
150
  #### Examples
176
151
 
177
- To parse records from a LevelDB folder, use the following command:
178
-
179
- ```
180
- dfleveldb db -s SOURCE
181
- ```
182
-
183
- To parse records from a LevelDB folder, and use the sequence number to
184
- determine recovered records and output as JSON, use the
185
- following command:
186
-
187
- ```
188
- dfleveldb db -s SOURCE --use_sequence_number
189
- ```
190
-
191
- To parse blocks / physical records/ write batches / internal key records from a
192
- LevelDB log (.log) file, use the following command, specifying the type (block,
193
- physical_records, etc) via the `-t` option. By default, internal key records are parsed:
194
-
195
- ```
196
- $ dfleveldb log -s SOURCE [-t {blocks,physical_records,write_batches,parsed_internal_key}]
197
- ```
198
-
199
- To parse blocks / records from a LevelDB table (.ldb) file, use the following
200
- command, specifying the type (blocks, records) via the `-t` option. By
201
- default, records are parsed:
202
-
203
- ```
204
- $ dfleveldb ldb -s SOURCE [-t {blocks,records}]
205
- ```
206
-
207
- To parse version edit records from a Descriptor (MANIFEST) file, use the
208
- following command:
209
-
210
- ```
211
- $ dfleveldb descriptor -s SOURCE [-o {json,jsonl,repr}] [-t {blocks,physical_records,versionedit} | -v]
212
- ```
213
-
214
- #### Plugins
152
+ | Source | Type | Command |
153
+ | :--- | :--- | :--- |
154
+ | **LevelDB Folder** | Records | `dfleveldb db -s SOURCE` |
155
+ | **Log file** (.log) | Physical Records | `dfleveldb log -s SOURCE -t physical_records` |
156
+ | **Log file** (.log) | Blocks | `dfleveldb log -s SOURCE -t blocks` |
157
+ | **Log file** (.log) | Write Batches | `dfleveldb log -s SOURCE -t write_batches` |
158
+ | **Log file** (.log) | Internal Key Records | `dfleveldb log -s SOURCE -t parsed_internal_key` |
159
+ | **Table file** (.ldb) | Records | `dfleveldb ldb -s SOURCE -t record` |
160
+ | **Table file** (.ldb) | Blocks | `dfleveldb ldb -s SOURCE -t blocks` |
161
+ | **Descriptor** (MANIFEST) | Version Edits | `dfleveldb descriptor -s SOURCE -t versionedit` |
162
+
163
+ #### Optional Plugins
215
164
 
216
165
  To apply a plugin parser for a leveldb file/folder, add the
217
166
  `--plugin [Plugin Name]` argument. Currently, there is support for the
@@ -1,23 +1,24 @@
1
1
  dfindexeddb/__init__.py,sha256=KPYL9__l8od6_OyDfGRTgaJ6iy_fqIgZ-dS2S-e3Rac,599
2
2
  dfindexeddb/errors.py,sha256=PNpwyf_lrPc4TE77oAakX3mu5D_YcP3f80wq8Y1LkvY,749
3
3
  dfindexeddb/utils.py,sha256=gep43m3LpAZNnW6JFOQTDn8MRrEXzGlc1NZ5yMuBsvI,10194
4
- dfindexeddb/version.py,sha256=C6239PGrB1o3OzfiWQ-W1g11b6xwmAfs53zspL7Ub1w,758
4
+ dfindexeddb/version.py,sha256=Cc6Drn2uY77dYocXLvSTJsbyUur_ujj6hChTvvNhnnY,758
5
5
  dfindexeddb/indexeddb/__init__.py,sha256=kExXSVBCTKCD5BZJkdMfUMqGksH-DMJxP2_lI0gq-BE,575
6
- dfindexeddb/indexeddb/cli.py,sha256=gEVlE6ZRAu360Oc5dePVQEupKLEGQUt6mUljlbptIUE,7686
6
+ dfindexeddb/indexeddb/cli.py,sha256=EbPmRloa95xoDlBhwH9ShgMGb8z118CZN_oarFtbZDE,12419
7
7
  dfindexeddb/indexeddb/types.py,sha256=m9llt7PTVMSvhsQ_uDIlZJUkbM_5k3gUx5CtMc3TvC8,1893
8
8
  dfindexeddb/indexeddb/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  dfindexeddb/indexeddb/chromium/__init__.py,sha256=kExXSVBCTKCD5BZJkdMfUMqGksH-DMJxP2_lI0gq-BE,575
10
10
  dfindexeddb/indexeddb/chromium/blink.py,sha256=oQdbgzn4w1dUs3jAqtCF0078o4ccupHSjH0hB42vJSc,32978
11
- dfindexeddb/indexeddb/chromium/definitions.py,sha256=cNFUhE9ngd_-NSNnH-0sXNRaX1keNrN6UKvF_ADaYds,8864
12
- dfindexeddb/indexeddb/chromium/record.py,sha256=AwZTvgjwj3wOPSNh2S94SXD2Ynhi8l1xqTB1asWp3vg,49373
11
+ dfindexeddb/indexeddb/chromium/definitions.py,sha256=HFe2aiAMCOrGkncZX80wzQVvaka8MzDpDZ8I1vE0HMs,10732
12
+ dfindexeddb/indexeddb/chromium/record.py,sha256=3U_NzidU-pAIFo1bHZucupi44-QmYXitYU9f9VA3yjI,56224
13
+ dfindexeddb/indexeddb/chromium/sqlite.py,sha256=fT3fmDuM3KgQD6OKhRrgYkg3ranfFN3BQZmr8DEt5PE,11476
13
14
  dfindexeddb/indexeddb/chromium/v8.py,sha256=cbaFQmhZoJMWGphLdvz1ZcvTge9MSh6GLH4K1yQNiWk,21888
14
15
  dfindexeddb/indexeddb/firefox/__init__.py,sha256=kExXSVBCTKCD5BZJkdMfUMqGksH-DMJxP2_lI0gq-BE,575
15
16
  dfindexeddb/indexeddb/firefox/definitions.py,sha256=C9qziLvNMmWFwVrt6K_lw4I_1YbiWJ9nxb65x0TefvU,4306
16
17
  dfindexeddb/indexeddb/firefox/gecko.py,sha256=rcph8PrX0IL_67cCvx9EsbEK_8r8lkXvW-nU7_2IsxA,19932
17
- dfindexeddb/indexeddb/firefox/record.py,sha256=wm0jHuTK47Qp6NPb2dQzRcqHnFTxOaPIz9sDHqb8gSc,6878
18
+ dfindexeddb/indexeddb/firefox/record.py,sha256=8T2s9e66qNUugCEqOLG3mtpq309njGkY20yrdek_Kio,7314
18
19
  dfindexeddb/indexeddb/safari/__init__.py,sha256=kExXSVBCTKCD5BZJkdMfUMqGksH-DMJxP2_lI0gq-BE,575
19
20
  dfindexeddb/indexeddb/safari/definitions.py,sha256=_st96KuCjgzAeltDGylmoojlCtVvTGQPWwqzYCuX3s8,2793
20
- dfindexeddb/indexeddb/safari/record.py,sha256=TKD1ltTOzhjKKU-3sCy7zm4H2r4NAZjlNel57m4BDZo,8132
21
+ dfindexeddb/indexeddb/safari/record.py,sha256=vwAud6yxlGmhT4UGvojBPjJ7NuIIsNkr7liJjY1yBj0,8874
21
22
  dfindexeddb/indexeddb/safari/webkit.py,sha256=16Vj7f3Pbts2ebdFg1dXmqQ3ixAKKv4_3znvsP21b4U,20816
22
23
  dfindexeddb/leveldb/__init__.py,sha256=KPYL9__l8od6_OyDfGRTgaJ6iy_fqIgZ-dS2S-e3Rac,599
23
24
  dfindexeddb/leveldb/cli.py,sha256=7CkM9P0bk83uj1cJrGFai82FIPBF0sul1tI2KUTMokA,10291
@@ -26,15 +27,15 @@ dfindexeddb/leveldb/descriptor.py,sha256=Cmy9y73cBeb586pryoagZ0-Zb2iA_8MCRGu6SN5
26
27
  dfindexeddb/leveldb/ldb.py,sha256=7sjpqMXrU6iXz5d4GAx58BuEknpjPM3qlrzNv6jdLIk,8140
27
28
  dfindexeddb/leveldb/log.py,sha256=oCMpLH8STOSK0qM-u44wnf781ehy5-JPe1e_osMlgL4,9510
28
29
  dfindexeddb/leveldb/record.py,sha256=Fnz7i446IO3IjJZAlB9olM_VSiEaM2ONHzG7BqrBzak,11978
29
- dfindexeddb/leveldb/utils.py,sha256=-70BjnttqGHHG1Ze9r8Pxspp6LGbRqHxXb3FODyXF6I,3879
30
+ dfindexeddb/leveldb/utils.py,sha256=Nl4JT_rnQZnHA6APhM_c8tPS9U6R2EAM4KxnclbRqTg,6476
30
31
  dfindexeddb/leveldb/plugins/__init__.py,sha256=RoC6tRkq8FhqIaFs6jwu1fao_qaSvlSfIFxQVjWregI,690
31
32
  dfindexeddb/leveldb/plugins/chrome_notifications.py,sha256=H2TgriK_mre1kflTh2Zd2aIvcoHXa_qJum5f5Sbwp88,5485
32
33
  dfindexeddb/leveldb/plugins/interface.py,sha256=1kFbhzgiRLQ18gdOc-pzbizQ7dp_Li-ut4ZHjXSxKIU,1193
33
34
  dfindexeddb/leveldb/plugins/manager.py,sha256=UV8dtdeu6xPCNzvx3fSef1WrZxGPzkPBD8mD2qpoVzY,2317
34
35
  dfindexeddb/leveldb/plugins/notification_database_data_pb2.py,sha256=DCPZHbyq2szLgrBprOKrJKycKJma8Z_SnAQM6Jx9bZg,4389
35
- dfindexeddb-20251109.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
36
- dfindexeddb-20251109.dist-info/METADATA,sha256=pewsavAQMLj0KqCnAxod4ZswNQ02HV9dBXEIEirLiLc,6015
37
- dfindexeddb-20251109.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
- dfindexeddb-20251109.dist-info/entry_points.txt,sha256=WG9YNLZ9lBx4Q9QF6wS4dZdZfADT3Zs4_-MV5TcA0ls,102
39
- dfindexeddb-20251109.dist-info/top_level.txt,sha256=X9OTaub1c8S_JJ7g-f8JdkhhdiZ4x1j4eni1hdUCwE4,12
40
- dfindexeddb-20251109.dist-info/RECORD,,
36
+ dfindexeddb-20260205.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
37
+ dfindexeddb-20260205.dist-info/METADATA,sha256=q0VwkHYGOmrEp7Up_dPYGwXPdULB4MxI1y_dWH5IGAE,5520
38
+ dfindexeddb-20260205.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
39
+ dfindexeddb-20260205.dist-info/entry_points.txt,sha256=WG9YNLZ9lBx4Q9QF6wS4dZdZfADT3Zs4_-MV5TcA0ls,102
40
+ dfindexeddb-20260205.dist-info/top_level.txt,sha256=X9OTaub1c8S_JJ7g-f8JdkhhdiZ4x1j4eni1hdUCwE4,12
41
+ dfindexeddb-20260205.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5