dfindexeddb 20240519__py3-none-any.whl → 20241031__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.
@@ -0,0 +1,180 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright 2024 Google LLC
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # https://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ """Firefox IndexedDB records."""
16
+ from dataclasses import dataclass
17
+ import sqlite3
18
+ import sys
19
+ import traceback
20
+ from typing import Any, Generator, Optional
21
+
22
+ from dfindexeddb import errors
23
+ from dfindexeddb.indexeddb.firefox import gecko
24
+
25
+
26
+ @dataclass
27
+ class FirefoxObjectStoreInfo:
28
+ """A FireFox ObjectStoreInfo.
29
+
30
+ Attributes:
31
+ id: the object store ID.
32
+ name: the object store name.
33
+ key_path: the object store key path.
34
+ auto_inc: the current auto-increment value.
35
+ database_name: the database name from the database table.
36
+ """
37
+ id: int
38
+ name: str
39
+ key_path: str
40
+ auto_inc: int
41
+ database_name: str
42
+
43
+
44
+ @dataclass
45
+ class FirefoxIndexedDBRecord:
46
+ """A Firefox IndexedDBRecord.
47
+
48
+ Attributes:
49
+ key: the parsed key.
50
+ value: the parsed value.
51
+ file_ids: the file identifiers.
52
+ object_store_id: the object store id.
53
+ object_store_name: the object store name from the object_store table.
54
+ database_name: the IndexedDB database name from the database table.
55
+ """
56
+ key: Any
57
+ value: Any
58
+ file_ids: Optional[str]
59
+ object_store_id: int
60
+ object_store_name: str
61
+ database_name: str
62
+
63
+
64
+ class FileReader:
65
+ """A reader for Firefox IndexedDB sqlite3 files.
66
+
67
+ Attributes:
68
+ database_name: the database name.
69
+ origin: the database origin.
70
+ metadata_version: the metadata version.
71
+ last_vacuum_time: the last vacuum time.
72
+ last_analyze_time: the last analyze time.
73
+ """
74
+
75
+ def __init__(self, filename: str):
76
+ """Initializes the FileReader.
77
+
78
+ Args:
79
+ filename: the IndexedDB filename.
80
+ """
81
+ self.filename = filename
82
+
83
+ with sqlite3.connect(f'file:{self.filename}?mode=ro', uri=True) as conn:
84
+ cursor = conn.execute(
85
+ 'SELECT name, origin, version, last_vacuum_time, last_analyze_time '
86
+ 'FROM database')
87
+ result = cursor.fetchone()
88
+ self.database_name = result[0]
89
+ self.origin = result[1]
90
+ self.metadata_version = result[2]
91
+ self.last_vacuum_time = result[3]
92
+ self.last_analyze_time = result[4]
93
+
94
+ def _ParseKey(self, key: bytes) -> Any:
95
+ """Parses a key."""
96
+ try:
97
+ return gecko.IDBKey.FromBytes(key)
98
+ except errors.ParserError as e:
99
+ print('failed to parse', key, file=sys.stderr)
100
+ traceback.print_exception(type(e), e, e.__traceback__)
101
+ return key
102
+
103
+ def _ParseValue(self, value: bytes) -> Any:
104
+ """Parses a value."""
105
+ try:
106
+ return gecko.JSStructuredCloneDecoder.FromBytes(value)
107
+ except errors.ParserError as err:
108
+ print('failed to parse', value, file=sys.stderr)
109
+ traceback.print_exception(type(err), err, err.__traceback__)
110
+ return value
111
+
112
+ def ObjectStores(self) -> Generator[FirefoxObjectStoreInfo, None, None]:
113
+ """Returns the Object Store information from the IndexedDB database.
114
+
115
+ Yields:
116
+ FirefoxObjectStoreInfo instances.
117
+ """
118
+ with sqlite3.connect(f'file:{self.filename}?mode=ro', uri=True) as conn:
119
+ cursor = conn.execute(
120
+ 'SELECT id, auto_increment, name, key_path FROM object_store')
121
+ results = cursor.fetchall()
122
+ for result in results:
123
+ yield FirefoxObjectStoreInfo(
124
+ id=result[0],
125
+ name=result[2],
126
+ key_path=result[3],
127
+ auto_inc=result[1],
128
+ database_name=self.database_name)
129
+
130
+ def RecordsByObjectStoreId(
131
+ self,
132
+ object_store_id: int
133
+ ) -> Generator[FirefoxIndexedDBRecord, None, None]:
134
+ """Returns FirefoxIndexedDBRecords by a given object store id.
135
+
136
+ Args:
137
+ object_store_id: the object store id.
138
+ """
139
+ with sqlite3.connect(f'file:{self.filename}?mode=ro', uri=True) as conn:
140
+ conn.text_factory = bytes
141
+ cursor = conn.execute(
142
+ 'SELECT od.key, od.data, od.object_store_id, od.file_ids, os.name '
143
+ 'FROM object_data od '
144
+ 'JOIN object_store os ON od.object_store_id == os.id '
145
+ 'WHERE os.id = ? ORDER BY od.key', (object_store_id, ))
146
+ for row in cursor:
147
+ key = self._ParseKey(row[0])
148
+ if row[3]:
149
+ value = row[1]
150
+ else:
151
+ value = self._ParseValue(row[1])
152
+ yield FirefoxIndexedDBRecord(
153
+ key=key,
154
+ value=value,
155
+ object_store_id=row[2],
156
+ file_ids=row[3],
157
+ object_store_name=row[4].decode('utf-8'),
158
+ database_name=self.database_name)
159
+
160
+ def Records(self) -> Generator[FirefoxIndexedDBRecord, None, None]:
161
+ """Returns FirefoxIndexedDBRecords from the database."""
162
+ with sqlite3.connect(f'file:{self.filename}?mode=ro', uri=True) as conn:
163
+ conn.text_factory = bytes
164
+ cursor = conn.execute(
165
+ 'SELECT od.key, od.data, od.object_store_id, od.file_ids, os.name '
166
+ 'FROM object_data od '
167
+ 'JOIN object_store os ON od.object_store_id == os.id')
168
+ for row in cursor:
169
+ key = self._ParseKey(row[0])
170
+ if row[3]:
171
+ value = row[1]
172
+ else:
173
+ value = self._ParseValue(row[1])
174
+ yield FirefoxIndexedDBRecord(
175
+ key=key,
176
+ value=value,
177
+ object_store_id=row[2],
178
+ file_ids=row[3],
179
+ object_store_name=row[4].decode('utf-8'),
180
+ database_name=self.database_name)
@@ -82,15 +82,22 @@ class FileList:
82
82
  files: List[FileData]
83
83
 
84
84
 
85
- class JSArray(list):
86
- """A parsed JavaScript array.
85
+ class JSArray:
86
+ """A parsed Javascript array.
87
87
 
88
- This is a wrapper around a standard Python list to allow assigning arbitrary
89
- properties as is possible in the JavaScript equivalent.
88
+ A Javascript array behaves like a Python list but allows assigning arbitrary
89
+ properties. The array is stored in the attribute __array__.
90
90
  """
91
+ def __init__(self):
92
+ self.__array__ = []
93
+
94
+ def Append(self, element: Any):
95
+ """Appends a new element to the array."""
96
+ self.__array__.append(element)
91
97
 
92
98
  def __repr__(self):
93
- array_entries = ", ".join([str(entry) for entry in list(self)])
99
+ array_entries = ", ".join(
100
+ [str(entry) for entry in list(self.__array__)])
94
101
  properties = ", ".join(
95
102
  f'{key}: {value}' for key, value in self.properties.items())
96
103
  return f'[{array_entries}, {properties}]'
@@ -100,6 +107,11 @@ class JSArray(list):
100
107
  """Returns the object properties."""
101
108
  return self.__dict__
102
109
 
110
+ def __eq__(self, other: JSArray):
111
+ return (
112
+ self.__array__ == other.__array__
113
+ and self.properties == other.properties)
114
+
103
115
  def __contains__(self, item):
104
116
  return item in self.__dict__
105
117
 
@@ -107,24 +119,36 @@ class JSArray(list):
107
119
  return self.__dict__[name]
108
120
 
109
121
 
110
- class JSSet(set):
111
- """A parsed JavaScript set.
122
+ class JSSet:
123
+ """A parsed Javascript set.
112
124
 
113
- This is a wrapper around a standard Python set to allow assigning arbitrary
114
- properties as is possible in the JavaScript equivalent.
125
+ A Javascript set behaves like a Python set but allows assigning arbitrary
126
+ properties. The array is stored in the attribute __set__.
115
127
  """
128
+ def __init__(self):
129
+ self.__set__ = set()
130
+
131
+ def Add(self, element: Any):
132
+ """Adds a new element to the set."""
133
+ self.__set__.add(element)
116
134
 
117
135
  def __repr__(self):
118
- set_entries = ", ".join([str(entry) for entry in list(self)])
136
+ array_entries = ", ".join(
137
+ [str(entry) for entry in list(self.__set__)])
119
138
  properties = ", ".join(
120
139
  f'{key}: {value}' for key, value in self.properties.items())
121
- return f'[{set_entries}, {properties}]'
140
+ return f'[{array_entries}, {properties}]'
122
141
 
123
142
  @property
124
143
  def properties(self) -> Dict[str, Any]:
125
144
  """Returns the object properties."""
126
145
  return self.__dict__
127
146
 
147
+ def __eq__(self, other: JSSet):
148
+ return (
149
+ self.__set__ == other.__set__
150
+ and self.properties == other.properties)
151
+
128
152
  def __contains__(self, item):
129
153
  return item in self.__dict__
130
154
 
@@ -294,7 +318,7 @@ class SerializedScriptValueDecoder():
294
318
  for _ in range(length):
295
319
  _, _ = self.decoder.DecodeUint32()
296
320
  _, value = self.DecodeValue()
297
- array.append(value)
321
+ array.Append(value)
298
322
 
299
323
  offset, terminator_tag = self.decoder.DecodeUint32()
300
324
  if terminator_tag != definitions.TerminatorTag:
@@ -480,7 +504,7 @@ class SerializedScriptValueDecoder():
480
504
 
481
505
  while tag != definitions.SerializationTag.NON_SET_PROPERTIES:
482
506
  _, key = self.DecodeValue()
483
- js_set.add(key)
507
+ js_set.Add(key)
484
508
  tag = self.PeekSerializationTag()
485
509
 
486
510
  # consume the NonSetPropertiesTag
@@ -0,0 +1,71 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright 2024 Google LLC
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # https://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ """Types for indexeddb."""
16
+ from __future__ import annotations
17
+
18
+ import dataclasses
19
+ from typing import Any, Dict, List, Set
20
+
21
+
22
+ @dataclasses.dataclass
23
+ class JSArray:
24
+ """A parsed Javascript array.
25
+
26
+ A JavaScript array behaves like a Python list but allows assigning arbitrary
27
+ properties. The array is stored in the attribute __array__.
28
+
29
+ Attributes:
30
+ values: the array values.
31
+ properties: the array properties.
32
+ """
33
+ values: List[Any] = dataclasses.field(default_factory=list)
34
+ properties: Dict[Any, Any] = dataclasses.field(default_factory=dict)
35
+
36
+
37
+ @dataclasses.dataclass
38
+ class JSSet:
39
+ """A parsed JavaScript set.
40
+
41
+ A JavaScript set behaves like a Python set but allows assigning arbitrary
42
+ properties. The array is stored in the attribute __set__.
43
+
44
+ Attributes:
45
+ values: the set values.
46
+ properties: the set properties.
47
+ """
48
+ values: Set[Any] = dataclasses.field(default_factory=set)
49
+ properties: Dict[Any, Any] = dataclasses.field(default_factory=dict)
50
+
51
+
52
+ @dataclasses.dataclass
53
+ class Null:
54
+ """A parsed JavaScript Null."""
55
+
56
+
57
+ @dataclasses.dataclass
58
+ class RegExp:
59
+ """A parsed JavaScript RegExp.
60
+
61
+ Attributes:
62
+ pattern: the pattern.
63
+ flags: the flags.
64
+ """
65
+ pattern: str
66
+ flags: int
67
+
68
+
69
+ @dataclasses.dataclass
70
+ class Undefined:
71
+ """A JavaScript undef."""
@@ -384,5 +384,6 @@ class FileReader:
384
384
 
385
385
  def GetLatestVersion(self) -> LevelDBVersion:
386
386
  """Returns the latest LevelDBVersion instance."""
387
- *_, latest = self.GetVersions()
387
+ for version in self.GetVersions():
388
+ latest = version
388
389
  return latest
dfindexeddb/utils.py CHANGED
@@ -213,7 +213,7 @@ T = TypeVar('T')
213
213
 
214
214
 
215
215
  class FromDecoderMixin:
216
- """A mixin for parsing dataclass attributes using a LevelDBDecoder."""
216
+ """A mixin for parsing dataclass attributes using a StreamDecoder."""
217
217
 
218
218
  @classmethod
219
219
  def FromDecoder(
dfindexeddb/version.py CHANGED
@@ -15,7 +15,7 @@
15
15
  """Version information for dfIndexeddb."""
16
16
 
17
17
 
18
- __version__ = "20240519"
18
+ __version__ = "20241031"
19
19
 
20
20
 
21
21
  def GetVersion():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dfindexeddb
3
- Version: 20240519
3
+ Version: 20241031
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>
@@ -230,8 +230,7 @@ analysis of IndexedDB and LevelDB files.
230
230
 
231
231
  It parses LevelDB, IndexedDB and JavaScript structures from these files without
232
232
  requiring native libraries. (Note: only a subset of IndexedDB key types and
233
- JavaScript types for Safari and Chromium-based browsers are currently supported.
234
- Firefox is under development).
233
+ JavaScript types for Firefox, Safari and Chromium-based browsers are currently supported).
235
234
 
236
235
  The content of IndexedDB files is dependent on what a web application stores
237
236
  locally/offline using the web browser's
@@ -313,6 +312,13 @@ options:
313
312
 
314
313
  #### Examples:
315
314
 
315
+ To parse IndexedDB records from an sqlite file for Firefox and output the
316
+ results as JSON, use the following command:
317
+
318
+ ```
319
+ dfindexeddb db -s SOURCE --format firefox -o json
320
+ ```
321
+
316
322
  To parse IndexedDB records from an sqlite file for Safari and output the
317
323
  results as JSON-L, use the following command:
318
324
 
@@ -376,7 +382,7 @@ To parse records from a LevelDB folder, use the following command:
376
382
  dfleveldb db -s SOURCE
377
383
  ```
378
384
 
379
- To parse records from a LevelDB folder, and use the sequence number to
385
+ To parse records from a LevelDB folder, and use the sequence number to
380
386
  determine recovered records and output as JSON, use the
381
387
  following command:
382
388
 
@@ -409,8 +415,8 @@ $ dfleveldb descriptor -s SOURCE [-o {json,jsonl,repr}] [-t {blocks,physical_rec
409
415
 
410
416
  #### Plugins
411
417
 
412
- To apply a plugin parser for a leveldb file/folder, add the
413
- `--plugin [Plugin Name]` argument. Currently, there is support for the
418
+ To apply a plugin parser for a leveldb file/folder, add the
419
+ `--plugin [Plugin Name]` argument. Currently, there is support for the
414
420
  following artifacts:
415
421
 
416
422
  | Plugin Name | Artifact Name |
@@ -1,24 +1,28 @@
1
1
  dfindexeddb/__init__.py,sha256=KPYL9__l8od6_OyDfGRTgaJ6iy_fqIgZ-dS2S-e3Rac,599
2
2
  dfindexeddb/errors.py,sha256=PNpwyf_lrPc4TE77oAakX3mu5D_YcP3f80wq8Y1LkvY,749
3
- dfindexeddb/utils.py,sha256=NEqnBNgAmSrZ7iwhEZx8PYlDrT8c-NnTLbm86ZLsOsc,9998
4
- dfindexeddb/version.py,sha256=PzaqD9kWSx3q6RDQLgClQ7dmnrp2h8nqJcq5jprK2uY,751
3
+ dfindexeddb/utils.py,sha256=g-uqQzT_iKM7PPEIuSCNkQG2ltwpnLpRA_dPtrgVzzc,9997
4
+ dfindexeddb/version.py,sha256=UAMCa51yvzp9xG8y5eagDkYAfzVLED94Kp7hTfVBSSI,751
5
5
  dfindexeddb/indexeddb/__init__.py,sha256=kExXSVBCTKCD5BZJkdMfUMqGksH-DMJxP2_lI0gq-BE,575
6
- dfindexeddb/indexeddb/cli.py,sha256=WeOaQXFZglkI9Mwc97rCnVsCX32Dw0CgMQX3RKutwIA,6339
6
+ dfindexeddb/indexeddb/cli.py,sha256=O07_DpVeGtYx2V-jUJF5Oc3OftM2FPLSwNdS45MLCdo,7435
7
+ dfindexeddb/indexeddb/types.py,sha256=cIXmShUbbXJMSbXkmthxGGrpIF9fWr3Ypfl6ckGoSBU,1892
7
8
  dfindexeddb/indexeddb/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
9
  dfindexeddb/indexeddb/chromium/__init__.py,sha256=kExXSVBCTKCD5BZJkdMfUMqGksH-DMJxP2_lI0gq-BE,575
9
10
  dfindexeddb/indexeddb/chromium/blink.py,sha256=kwhPfzcWOOxYyXUWfV6f4grQwXzS2ABFaNVMIVhol3c,32268
10
11
  dfindexeddb/indexeddb/chromium/definitions.py,sha256=1a-AmHVZ95uDB6se_fdarwJR8q0tFMQNh2xrZ2-VxN8,8739
11
12
  dfindexeddb/indexeddb/chromium/record.py,sha256=LIuTwwQeQbn6CBXdo0AZZHounOWcnXRg6W082yxmNBo,47578
12
- dfindexeddb/indexeddb/chromium/v8.py,sha256=NsbMgA6nRcAfdLg6CFwWadwsDS6TJ95-4MrgphaTuLw,22102
13
+ dfindexeddb/indexeddb/chromium/v8.py,sha256=tzkKhx0S53HLCNMCls4GCFXrRCjjrgkFSwL3mbNQsjg,22656
13
14
  dfindexeddb/indexeddb/firefox/__init__.py,sha256=kExXSVBCTKCD5BZJkdMfUMqGksH-DMJxP2_lI0gq-BE,575
15
+ dfindexeddb/indexeddb/firefox/definitions.py,sha256=xkvlYaaFR2IDQBGJGnrhVIOUce6VuMq-kWXe2CLX3Aw,4306
16
+ dfindexeddb/indexeddb/firefox/gecko.py,sha256=m6-tGHOFnND-XG4C-9o1Atxo4BkHTMhFWCGqW2vFVPk,19423
17
+ dfindexeddb/indexeddb/firefox/record.py,sha256=yB7dYiwzCx1c67Sf6ViMSX51SCrcgU8OBUaVYczqTik,5766
14
18
  dfindexeddb/indexeddb/safari/__init__.py,sha256=kExXSVBCTKCD5BZJkdMfUMqGksH-DMJxP2_lI0gq-BE,575
15
19
  dfindexeddb/indexeddb/safari/definitions.py,sha256=nW8MmYx9Ob86W4pxm4QD4Xvr5QjoV34-U7wDhm2GIr0,2779
16
20
  dfindexeddb/indexeddb/safari/record.py,sha256=bzoMSgpXs2SsEOKHjVh9tkJDZtzGkQByq3G5dK_Yd7Q,8010
17
- dfindexeddb/indexeddb/safari/webkit.py,sha256=SmmwuJKF8NfHCiAaz1Zc1LsVm6IF3bTkaEzi6M0-HdM,21969
21
+ dfindexeddb/indexeddb/safari/webkit.py,sha256=LHaSLOGr74dzGblrqC_RVYD6GCftzGP-p0oWujZ3l1c,22592
18
22
  dfindexeddb/leveldb/__init__.py,sha256=KPYL9__l8od6_OyDfGRTgaJ6iy_fqIgZ-dS2S-e3Rac,599
19
23
  dfindexeddb/leveldb/cli.py,sha256=e2C94FSP28dh83FWQXD5N44ymUDwkfFeX0Tfk9YLCTo,9913
20
24
  dfindexeddb/leveldb/definitions.py,sha256=lPW_kjc47vyoGOoEWfgWvKcpGbN-0h7XXwCeMoFmYKk,1486
21
- dfindexeddb/leveldb/descriptor.py,sha256=WR3irG16oIE6VbaP9UPnzOD3KlHR8GYFnoeG6ySJUzU,12211
25
+ dfindexeddb/leveldb/descriptor.py,sha256=BgWO-sEqT2zhPu9oEplTa8O_szpgU2N4QfDZeroTcx0,12237
22
26
  dfindexeddb/leveldb/ldb.py,sha256=mN-M7PLtE_VLZCbCbzRgjkSezbMUhgDjgWgPgIxJ1jM,8087
23
27
  dfindexeddb/leveldb/log.py,sha256=ofw0r2f_3Ll5oHzssvp61nmjhIPdt3tmb9UeNiGLHXk,9401
24
28
  dfindexeddb/leveldb/record.py,sha256=j7ZnU6VDVcYVpJRGFRb5Sr2edhC3aGp3U0kPNcoZgng,11912
@@ -28,10 +32,10 @@ dfindexeddb/leveldb/plugins/chrome_notifications.py,sha256=-dyb_AJbUPE2wPJg_Y1Ns
28
32
  dfindexeddb/leveldb/plugins/interface.py,sha256=QlNEvVvU8K9ChE2kblM97cOvXwvmCh9NuSf2b6WwezQ,1257
29
33
  dfindexeddb/leveldb/plugins/manager.py,sha256=jisYyks3OQQQUVACoGcWN81UCGQEa537YvYL7v3CiFs,2139
30
34
  dfindexeddb/leveldb/plugins/notification_database_data_pb2.py,sha256=DCPZHbyq2szLgrBprOKrJKycKJma8Z_SnAQM6Jx9bZg,4389
31
- dfindexeddb-20240519.dist-info/AUTHORS,sha256=QbvjbAom57fpEkekkCVFUj0B9KUMGraR510aUMBC-PE,286
32
- dfindexeddb-20240519.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
33
- dfindexeddb-20240519.dist-info/METADATA,sha256=aDy87_6iqVwvkSI4teKm-Sq8DKb6GHpuydePrZXsydA,18818
34
- dfindexeddb-20240519.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
35
- dfindexeddb-20240519.dist-info/entry_points.txt,sha256=WG9YNLZ9lBx4Q9QF6wS4dZdZfADT3Zs4_-MV5TcA0ls,102
36
- dfindexeddb-20240519.dist-info/top_level.txt,sha256=X9OTaub1c8S_JJ7g-f8JdkhhdiZ4x1j4eni1hdUCwE4,12
37
- dfindexeddb-20240519.dist-info/RECORD,,
35
+ dfindexeddb-20241031.dist-info/AUTHORS,sha256=QbvjbAom57fpEkekkCVFUj0B9KUMGraR510aUMBC-PE,286
36
+ dfindexeddb-20241031.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
37
+ dfindexeddb-20241031.dist-info/METADATA,sha256=0Wi-Bd_2GyLC3ssYcDQuPKiFK6s98gOgir-1QuYfTPo,18972
38
+ dfindexeddb-20241031.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
39
+ dfindexeddb-20241031.dist-info/entry_points.txt,sha256=WG9YNLZ9lBx4Q9QF6wS4dZdZfADT3Zs4_-MV5TcA0ls,102
40
+ dfindexeddb-20241031.dist-info/top_level.txt,sha256=X9OTaub1c8S_JJ7g-f8JdkhhdiZ4x1j4eni1hdUCwE4,12
41
+ dfindexeddb-20241031.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (75.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5