Skip to content

Commit

Permalink
Update JSSet/JSArray implementations
Browse files Browse the repository at this point in the history
  • Loading branch information
sydp committed May 19, 2024
1 parent d6cf6ff commit 04a67b5
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 30 deletions.
31 changes: 24 additions & 7 deletions dfindexeddb/indexeddb/chromium/v8.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,22 @@ class BufferArrayView:
flags: int


class JSArray(list):
class JSArray:
"""A parsed Javascript array.
This is a wrapper around a standard Python list to allow assigning arbitrary
properties as is possible in the Javascript equivalent.
A Javascript array behaves like a Python list but allows assigning arbitrary
properties. The array is stored in the attribute __array__.
"""
def __init__(self):
self.__array__ = []

def Append(self, element: Any):
"""Appends a new element to the array."""
self.__array__.append(element)

def __repr__(self):
array_entries = ", ".join([str(entry) for entry in list(self)])
array_entries = ", ".join(
[str(entry) for entry in list(self.__array__)])
properties = ", ".join(
f'{key}: {value}' for key, value in self.properties.items())
return f'[{array_entries}, {properties}]'
Expand All @@ -52,6 +59,11 @@ def properties(self) -> Dict[str, Any]:
"""Returns the object properties."""
return self.__dict__

def __eq__(self, other):
return (
self.__array__ == other.__array__
and self.properties == other.properties)

def __contains__(self, item):
return item in self.__dict__

Expand Down Expand Up @@ -194,6 +206,7 @@ def _ReadObject(self):
if tag and tag == definitions.V8SerializationTag.ARRAY_BUFFER_VIEW:
self._ConsumeTag(tag)
result = self._ReadJSArrayBufferView(result)
self.objects[self._GetNextId()] = result
return result

def _ReadObjectInternal(self) -> Tuple[definitions.V8SerializationTag, Any]:
Expand Down Expand Up @@ -385,7 +398,10 @@ def _ReadJSObjectProperties(
while self._PeekTag() != end_tag:
key = self._ReadObject()
value = self._ReadObject()
js_object[key] = value
if isinstance(js_object, dict):
js_object[key] = value
else:
js_object.properties[key] = value
num_properties += 1
self._ConsumeTag(end_tag)
return num_properties
Expand All @@ -407,7 +423,7 @@ def ReadSparseJSArray(self) -> JSArray:
js_array = JSArray()
_, length = self.decoder.DecodeUint32Varint()
for _ in range(length):
js_array.append(Undefined())
js_array.Append(Undefined())

num_properties = self._ReadJSObjectProperties(
js_array.__dict__, definitions.V8SerializationTag.END_SPARSE_JS_ARRAY)
Expand Down Expand Up @@ -440,7 +456,7 @@ def ReadDenseJSArray(self) -> JSArray:

if self.version < 11 and isinstance(array_object, Undefined):
continue
js_array.append(array_object)
js_array.Append(array_object)

num_properties = self._ReadJSObjectProperties(
js_array.__dict__, definitions.V8SerializationTag.END_DENSE_JS_ARRAY)
Expand Down Expand Up @@ -571,6 +587,7 @@ def _ReadJSArrayBuffer(
if is_resizable:
_, max_byte_length = self.decoder.DecodeUint32Varint()
if byte_length > max_byte_length:
self.objects[next_id] = array_buffer
return array_buffer
if byte_length:
_, array_buffer = self.decoder.ReadBytes(byte_length)
Expand Down
2 changes: 2 additions & 0 deletions dfindexeddb/indexeddb/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ def default(self, o):
return o.isoformat()
if isinstance(o, v8.Undefined):
return "<undefined>"
if isinstance(o, v8.JSArray):
return o.__dict__
if isinstance(o, v8.Null):
return "<null>"
if isinstance(o, set):
Expand Down
50 changes: 37 additions & 13 deletions dfindexeddb/indexeddb/safari/webkit.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,22 @@ class FileList:
files: List[FileData]


class JSArray(list):
"""A parsed JavaScript array.
class JSArray:
"""A parsed Javascript array.
This is a wrapper around a standard Python list to allow assigning arbitrary
properties as is possible in the JavaScript equivalent.
A Javascript array behaves like a Python list but allows assigning arbitrary
properties. The array is stored in the attribute __array__.
"""
def __init__(self):
self.__array__ = []

def Append(self, element: Any):
"""Appends a new element to the array."""
self.__array__.append(element)

def __repr__(self):
array_entries = ", ".join([str(entry) for entry in list(self)])
array_entries = ", ".join(
[str(entry) for entry in list(self.__array__)])
properties = ", ".join(
f'{key}: {value}' for key, value in self.properties.items())
return f'[{array_entries}, {properties}]'
Expand All @@ -100,31 +107,48 @@ def properties(self) -> Dict[str, Any]:
"""Returns the object properties."""
return self.__dict__

def __eq__(self, other):
return (
self.__array__ == other.__array__
and self.properties == other.properties)

def __contains__(self, item):
return item in self.__dict__

def __getitem__(self, name):
return self.__dict__[name]


class JSSet(set):
"""A parsed JavaScript set.
class JSSet:
"""A parsed Javascript set.
This is a wrapper around a standard Python set to allow assigning arbitrary
properties as is possible in the JavaScript equivalent.
A Javascript set behaves like a Python set but allows assigning arbitrary
properties. The array is stored in the attribute __set__.
"""
def __init__(self):
self.__set__ = set()

def Add(self, element: Any):
"""Adds a new element to the set."""
self.__set__.add(element)

def __repr__(self):
set_entries = ", ".join([str(entry) for entry in list(self)])
array_entries = ", ".join(
[str(entry) for entry in list(self.__set__)])
properties = ", ".join(
f'{key}: {value}' for key, value in self.properties.items())
return f'[{set_entries}, {properties}]'
return f'[{array_entries}, {properties}]'

@property
def properties(self) -> Dict[str, Any]:
"""Returns the object properties."""
return self.__dict__

def __eq__(self, other):
return (
self.__set__ == other.__set__
and self.properties == other.properties)

def __contains__(self, item):
return item in self.__dict__

Expand Down Expand Up @@ -294,7 +318,7 @@ def DecodeArray(self) -> JSArray:
for _ in range(length):
_, _ = self.decoder.DecodeUint32()
_, value = self.DecodeValue()
array.append(value)
array.Append(value)

offset, terminator_tag = self.decoder.DecodeUint32()
if terminator_tag != definitions.TerminatorTag:
Expand Down Expand Up @@ -480,7 +504,7 @@ def DecodeSetData(self) -> JSSet:

while tag != definitions.SerializationTag.NON_SET_PROPERTIES:
_, key = self.DecodeValue()
js_set.add(key)
js_set.Add(key)
tag = self.PeekSerializationTag()

# consume the NonSetPropertiesTag
Expand Down
10 changes: 8 additions & 2 deletions tests/dfindexeddb/indexeddb/chromium/v8.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,11 @@ def test_jsarray(self):
# > x['propa'] = 123
# > console.log(v8.serialize(x)).toString('hex'))
buffer = bytes.fromhex('ff0d4103490249044906220570726f706149f601240103')
expected_value = v8.JSArray([1, 2, 3])
expected_value = v8.JSArray()
expected_value.Append(1)
expected_value.Append(2)
expected_value.Append(3)
expected_value.properties['propa'] = 123
parsed_value = v8.ValueDeserializer.FromBytes(buffer, None)
self.assertEqual(parsed_value, expected_value)
self.assertTrue(hasattr(parsed_value, 'propa'))
Expand All @@ -174,7 +178,9 @@ def test_jsarray(self):
# > x.length = 10
# > console.log(v8.serialize(x)).toString('hex'))
buffer = bytes.fromhex('ff0d610a40000a')
expected_value = v8.JSArray([v8.Undefined() for i in range(10)])
expected_value = v8.JSArray()
for _ in range(10):
expected_value.Append(v8.Undefined())
parsed_value = v8.ValueDeserializer.FromBytes(buffer, None)
self.assertEqual(parsed_value, expected_value)

Expand Down
24 changes: 19 additions & 5 deletions tests/dfindexeddb/indexeddb/safari/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,9 +241,13 @@ def test_empty_string_object_record(self):

def test_set_record(self):
"""Tests for an IndexedDB record with a set value."""
expected_set = webkit.JSSet()
for i in range(1, 4):
expected_set.Add(i)

expected_record = record.IndexedDBRecord(
key=27,
value={'id': 27, 'value': {1, 2, 3}},
value={'id': 27, 'value': expected_set},
object_store_id=1,
object_store_name='test store a',
database_name='IndexedDB test',
Expand Down Expand Up @@ -289,6 +293,13 @@ def test_empty_object_record(self):

def test_mixed_object_record(self):
"""Tests for an IndexedDB record with mixed values within an object."""
expected_test_array = webkit.JSArray()
for value in [123, 456, 'abc', 'def']:
expected_test_array.Append(value)
expected_set = webkit.JSSet()
for i in range(1, 4):
expected_set.Add(i)

expected_record = record.IndexedDBRecord(
key=1,
value={
Expand All @@ -305,13 +316,13 @@ def test_mixed_object_record(self):
'test_boolean_false_object': False,
'test_bigint': 12300000000000001048576,
'test_date': datetime.datetime(2023, 2, 12, 23, 20, 30, 456000),
'test_set': set([1, 2, 3]),
'test_set': expected_set,
'test_map': {
'a': 1,
'b': 2,
'c': 3},
'test_regexp': webkit.RegExp('\\w+', ''),
'test_array': [123, 456, 'abc', 'def'],
'test_array': expected_test_array,
'test_object': {
'name': {
'first': 'Jane',
Expand Down Expand Up @@ -460,10 +471,13 @@ def test_buffer_key_record(self):

def test_array_key_record(self):
"""Tests for an IndexedDB record with an array in the key."""
expected_test_array = webkit.JSArray()
for value in [1, 2, 3]:
expected_test_array.Append(value)
expected_record = record.IndexedDBRecord(
key=[1, 2, 3],
key=[1.0, 2.0, 3.0],
value={
'id': [1, 2, 3],
'id': expected_test_array,
'value': {}},
object_store_id=1,
object_store_name='test store a',
Expand Down
17 changes: 14 additions & 3 deletions tests/dfindexeddb/indexeddb/safari/webkit.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,10 @@ def test_parse_set(self):
'0F00000002020000806964051B000000'
'0500008076616C75651D070502000000'
'050300000020FFFFFFFFFFFFFFFF')
expected_value = {'id': 27, 'value': {1, 2, 3}}
expected_set = webkit.JSSet()
for i in range(1, 4):
expected_set.Add(i)
expected_value = {'id': 27, 'value': expected_set}
parsed_value = webkit.SerializedScriptValueDecoder.FromBytes(
value_bytes)
self.assertEqual(parsed_value, expected_value)
Expand Down Expand Up @@ -268,6 +271,14 @@ def test_mixed_object(self):
'6A65637402040000806E616D650205000080666972737410040000804A616E65'
'040000806C6173741003000080446F65FFFFFFFF030000806167650515000000'
'FFFFFFFFFFFFFFFF')

expected_test_array = webkit.JSArray()
for value in [123, 456, 'abc', 'def']:
expected_test_array.Append(value)
expected_set = webkit.JSSet()
for i in range(1, 4):
expected_set.Add(i)

expected_value = {
'id': 1,
'test_undef': webkit.Undefined(),
Expand All @@ -282,13 +293,13 @@ def test_mixed_object(self):
'test_boolean_false_object': False,
'test_bigint': 12300000000000001048576,
'test_date': datetime.datetime(2023, 2, 12, 23, 20, 30, 456000),
'test_set': set([1, 2, 3]),
'test_set': expected_set,
'test_map': {
'a': 1,
'b': 2,
'c': 3},
'test_regexp': webkit.RegExp('\\w+', ''),
'test_array': [123, 456, 'abc', 'def'],
'test_array': expected_test_array,
'test_object': {
'name': {
'first': 'Jane',
Expand Down

0 comments on commit 04a67b5

Please sign in to comment.