Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into becca/katc-construct
Browse files Browse the repository at this point in the history
  • Loading branch information
RebeccaMahany committed Jul 2, 2024
2 parents 3019cf0 + 36afc33 commit 0c00c9e
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 187 deletions.
33 changes: 6 additions & 27 deletions ee/indexeddb/indexeddb.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ var indexeddbComparer = newChromeComparer()

// QueryIndexeddbObjectStore queries the indexeddb at the given location `dbLocation`,
// returning all objects in the given database that live in the given object store.
func QueryIndexeddbObjectStore(dbLocation string, dbName string, objectStoreName string) ([]map[string]any, error) {
func QueryIndexeddbObjectStore(dbLocation string, dbName string, objectStoreName string) ([]map[string][]byte, error) {
// If Chrome is open, we won't be able to open the db. So, copy it to a temporary location first.
tempDbCopyLocation, err := copyIndexeddb(dbLocation)
if err != nil {
Expand Down Expand Up @@ -91,42 +91,21 @@ func QueryIndexeddbObjectStore(dbLocation string, dbName string, objectStoreName
return nil, errors.New("unable to get object store ID")
}

// Get the key path for all objects in this store
keyPathRaw, err := db.Get(objectStoreKeyPathKey(databaseId, objectStoreId), nil)
if err != nil {
return nil, fmt.Errorf("getting key path: %w", err)
}
keyPath, err := decodeIDBKeyPath(keyPathRaw)
if err != nil {
return nil, fmt.Errorf("decoding key path: %w", err)
}

// Get the key prefix for all objects in this store.
keyPrefix := objectDataKeyPrefix(databaseId, objectStoreId)

// Now, we can read all records, parsing only the ones with our matching key prefix.
objs := make([]map[string]any, 0)
// Now, we can read all records, keeping only the ones with our matching key prefix.
objs := make([]map[string][]byte, 0)
iter := db.NewIterator(nil, nil)
for iter.Next() {
key := iter.Key()
if !bytes.HasPrefix(key, keyPrefix) {
continue
}

keyVal, err := decodeIDBKey(key, keyPrefix)
if err != nil {
return objs, fmt.Errorf("decoding key: %w", err)
}

obj, err := deserializeIndexeddbValue(iter.Value())
if err != nil {
return objs, fmt.Errorf("decoding object: %w", err)
}

// Set the key path in the object -- add idb_ prefix to avoid collisions
obj[fmt.Sprintf("idb_%s", string(keyPath))] = keyVal

objs = append(objs, obj)
objs = append(objs, map[string][]byte{
"data": iter.Value(),
})
}
iter.Release()
if err := iter.Error(); err != nil {
Expand Down
62 changes: 0 additions & 62 deletions ee/indexeddb/keys.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package indexeddb

import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"path/filepath"
"strings"
Expand All @@ -22,9 +20,6 @@ const (
// Index IDs
objectStoreDataIndexId = 0x01 // 1

// Key types
keyTypeNumber = 0x03 // 3

// When parsing the origin from the database location, I have to add @1 at the end for the origin to be complete.
// I don't know why.
originSuffix = "@1"
Expand Down Expand Up @@ -77,24 +72,6 @@ func objectStoreNameKey(dbId uint64, objectStoreId uint64) []byte {
return append(storeNameKey, 0x00)
}

// objectStoreKeyPathKey constructs a query for the key path for the object store with the given ID.
func objectStoreKeyPathKey(dbId uint64, objectStoreId uint64) []byte {
// Key takes the format <0, database id, 0, 0, 50, object store id, 1>.
storeNameKey := []byte{0x00}
storeNameKey = append(storeNameKey, uvarintToBytes(dbId)...)
storeNameKey = append(storeNameKey,
0x00,
0x00,
objectStoreMetaDataTypeByte,
)

// Add the object store ID
storeNameKey = append(storeNameKey, uvarintToBytes(objectStoreId)...)

// Add 0x01, indicating we're querying for the object store name
return append(storeNameKey, 0x01)
}

// objectDataKeyPrefix returns the key prefix shared by all objects stored in the given database
// and in the given store.
func objectDataKeyPrefix(dbId uint64, objectStoreId uint64) []byte {
Expand All @@ -110,45 +87,6 @@ func decodeUtf16BigEndianBytes(b []byte) ([]byte, error) {
return utf16BigEndianDecoder.Bytes(b)
}

// decodeIDBKeyPath extracts the key path from the given input. IDBKeyPaths have multiple types.
// This function only supports string types, which take the format 0x00, 0x00, 0x01, StringWithLength.
func decodeIDBKeyPath(b []byte) ([]byte, error) {
if !bytes.HasPrefix(b, []byte{0x00, 0x00, 0x01}) {
return nil, errors.New("unsupported IDBKeyPath type")
}

if len(b) < 4 {
return nil, fmt.Errorf("IDBKeyPath with length %d is too short to be a string", len(b))
}

// Read the StringWithLength's length, but discard it -- we can just decode the remainder
// of the slice.
prefixLen := 3
_, bytesRead := binary.Uvarint(b[prefixLen:])

return decodeUtf16BigEndianBytes(b[prefixLen+bytesRead:])
}

// decodeIDBKey extracts the object key from the given data. It currently only supports
// numerical keys.
func decodeIDBKey(key []byte, keyPrefix []byte) (any, error) {
key = bytes.TrimPrefix(key, keyPrefix)

// Next byte is key type.
switch key[0] {
case keyTypeNumber:
// IEEE754 64-bit (double), in host endianness
buf := bytes.NewReader(key[1:])
var keyData float64
if err := binary.Read(buf, binary.NativeEndian, &keyData); err != nil {
return nil, fmt.Errorf("reading double: %w", err)
}
return keyData, nil
default:
return nil, fmt.Errorf("unimplemented key type 0x%02x", key[0])
}
}

// stringWithLength constructs an appropriate representation of `s`.
// See: https://github.com/chromium/chromium/blob/main/content/browser/indexed_db/docs/leveldb_coding_scheme.md#types
func stringWithLength(s string) ([]byte, error) {
Expand Down
57 changes: 0 additions & 57 deletions ee/indexeddb/keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,26 +53,6 @@ func Test_objectStoreNameKey(t *testing.T) {
require.Equal(t, expectedKey, objectStoreNameKey(dbId, objectStoreId), "object store name key format is incorrect")
}

func Test_objectStoreKeyPathKey(t *testing.T) {
t.Parallel()

var dbId uint64 = 2
var objectStoreId uint64 = 3

// Key takes the format <0, database id, 0, 0, 50, object store id, 1>.
expectedKey := []byte{
0x00,
0x02, // DB ID
0x00,
0x00,
objectStoreMetaDataTypeByte,
0x03, // object store ID
0x01,
}

require.Equal(t, expectedKey, objectStoreKeyPathKey(dbId, objectStoreId), "object store key path key format is incorrect")
}

func Test_objectDataKeyPrefix(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -102,43 +82,6 @@ func Test_decodeUtf16BigEndianBytes(t *testing.T) {
require.Equal(t, originalBytes, actualBytes, "decoded bytes do not match")
}

func Test_decodeIDBKeyPath(t *testing.T) {
t.Parallel()

// Prepare key path
keyPath := []byte("id")
utf16BigEndianEncoder := unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM).NewEncoder()
utf16KeyPathBytes, err := utf16BigEndianEncoder.Bytes(keyPath)
require.NoError(t, err, "encoding bytes")

testKeyPath := []byte{
0x00, 0x00, 0x01, // prefix
0x02, // length of "id"
}
testKeyPath = append(testKeyPath, utf16KeyPathBytes...)

resultKeyPath, err := decodeIDBKeyPath(testKeyPath)
require.NoError(t, err, "decoding key path")
require.Equal(t, keyPath, resultKeyPath)
}

func Test_decodeIDBKey(t *testing.T) {
t.Parallel()

// Prepare key value
var keyValue float64 = 4
keyValueBuf := bytes.NewBuffer(make([]byte, 0))
require.NoError(t, binary.Write(keyValueBuf, binary.NativeEndian, keyValue), "writing key value")

testKeyPrefix := []byte{0x00, 0x01, 0x01, 0x01}
testKey := append(testKeyPrefix, keyTypeNumber)
testKey = append(testKey, keyValueBuf.Bytes()...)

actualVal, err := decodeIDBKey(testKey, testKeyPrefix)
require.NoError(t, err, "decoding idb key")
require.Equal(t, keyValue, actualVal)
}

func Test_stringWithLength(t *testing.T) {
t.Parallel()

Expand Down
Loading

0 comments on commit 0c00c9e

Please sign in to comment.