summaryrefslogtreecommitdiff
path: root/packages/idb-bridge
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2021-02-24 17:33:07 +0100
committerFlorian Dold <florian@dold.me>2021-02-24 17:33:07 +0100
commit564e4f8710388ab2ae40c959c497f2e0260199ed (patch)
tree1d9f1b835bf6580977158f5eee133f8fa39f67d4 /packages/idb-bridge
parentbc7956c2ba685e459c94204ca30f85eef881d0ac (diff)
downloadwallet-core-564e4f8710388ab2ae40c959c497f2e0260199ed.tar.gz
wallet-core-564e4f8710388ab2ae40c959c497f2e0260199ed.tar.bz2
wallet-core-564e4f8710388ab2ae40c959c497f2e0260199ed.zip
idb: encapsulate non-JSON data correctly
Diffstat (limited to 'packages/idb-bridge')
-rw-r--r--packages/idb-bridge/src/MemoryBackend.ts25
-rw-r--r--packages/idb-bridge/src/bridge-idb.ts20
-rw-r--r--packages/idb-bridge/src/idb-wpt-ported/idbindex-openCursor.test.ts30
-rw-r--r--packages/idb-bridge/src/util/canInjectKey.ts50
-rw-r--r--packages/idb-bridge/src/util/deepEquals.ts72
-rw-r--r--packages/idb-bridge/src/util/injectKey.ts69
-rw-r--r--packages/idb-bridge/src/util/makeStoreKeyValue.ts50
-rw-r--r--packages/idb-bridge/src/util/structuredClone.test.ts39
-rw-r--r--packages/idb-bridge/src/util/structuredClone.ts69
9 files changed, 166 insertions, 258 deletions
diff --git a/packages/idb-bridge/src/MemoryBackend.ts b/packages/idb-bridge/src/MemoryBackend.ts
index 68f60f756..9233e8d88 100644
--- a/packages/idb-bridge/src/MemoryBackend.ts
+++ b/packages/idb-bridge/src/MemoryBackend.ts
@@ -27,13 +27,12 @@ import {
StoreLevel,
RecordStoreResponse,
} from "./backend-interface";
-import { structuredClone, structuredRevive } from "./util/structuredClone";
import {
- InvalidStateError,
- InvalidAccessError,
- ConstraintError,
- DataError,
-} from "./util/errors";
+ structuredClone,
+ structuredEncapsulate,
+ structuredRevive,
+} from "./util/structuredClone";
+import { ConstraintError, DataError } from "./util/errors";
import BTree, { ISortedMapF } from "./tree/b+tree";
import { compareKeys } from "./util/cmp";
import { StoreKeyResult, makeStoreKeyValue } from "./util/makeStoreKeyValue";
@@ -269,6 +268,12 @@ export class MemoryBackend implements Backend {
);
}
+ if (typeof data !== "object") {
+ throw Error("db dump corrupt");
+ }
+
+ data = structuredRevive(data);
+
this.databases = {};
for (const dbName of Object.keys(data.databases)) {
@@ -390,7 +395,7 @@ export class MemoryBackend implements Backend {
};
dbDumps[dbName] = dbDump;
}
- return { databases: dbDumps };
+ return structuredEncapsulate({ databases: dbDumps });
}
async getDatabases(): Promise<{ name: string; version: number }[]> {
@@ -1322,7 +1327,7 @@ export class MemoryBackend implements Backend {
console.error("request was", req);
throw Error("invariant violated during read");
}
- values.push(structuredClone(result.value));
+ values.push(result.value);
}
}
} else {
@@ -1468,10 +1473,9 @@ export class MemoryBackend implements Backend {
}
let storeKeyResult: StoreKeyResult;
- const revivedValue = structuredRevive(storeReq.value);
try {
storeKeyResult = makeStoreKeyValue(
- revivedValue,
+ storeReq.value,
storeReq.key,
keygen,
autoIncrement,
@@ -1506,6 +1510,7 @@ export class MemoryBackend implements Backend {
}
const objectStoreRecord: ObjectStoreRecord = {
+ // FIXME: We should serialize the key here, not just clone it.
primaryKey: structuredClone(key),
value: structuredClone(value),
};
diff --git a/packages/idb-bridge/src/bridge-idb.ts b/packages/idb-bridge/src/bridge-idb.ts
index 0c309b421..1fa21c7d5 100644
--- a/packages/idb-bridge/src/bridge-idb.ts
+++ b/packages/idb-bridge/src/bridge-idb.ts
@@ -64,8 +64,6 @@ import { openPromise } from "./util/openPromise";
import queueTask from "./util/queueTask";
import {
structuredClone,
- structuredEncapsulate,
- structuredRevive,
} from "./util/structuredClone";
import { validateKeyPath } from "./util/validateKeyPath";
import { valueToKey } from "./util/valueToKey";
@@ -249,7 +247,7 @@ export class BridgeIDBCursor implements IDBCursor {
this._primaryKey = response.primaryKeys![0];
if (!this._keyOnly) {
- this._value = structuredRevive(response.values![0]);
+ this._value = response.values![0];
}
this._gotValue = true;
@@ -294,7 +292,7 @@ export class BridgeIDBCursor implements IDBCursor {
const storeReq: RecordStoreRequest = {
key: this._primaryKey,
- value: structuredEncapsulate(value),
+ value,
objectStoreName: this._objectStoreName,
storeLevel: StoreLevel.UpdateExisting,
};
@@ -1216,7 +1214,7 @@ export class BridgeIDBIndex implements IDBIndex {
if (!values) {
throw Error("invariant violated");
}
- return structuredRevive(values[0]);
+ return values[0];
};
return this._objectStore._transaction._execRequestAsync({
@@ -1260,7 +1258,7 @@ export class BridgeIDBIndex implements IDBIndex {
if (!values) {
throw Error("invariant violated");
}
- return values.map((x) => structuredRevive(x));
+ return values;
};
return this._objectStore._transaction._execRequestAsync({
@@ -1641,7 +1639,7 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
const result = await this._backend.storeRecord(btx, {
objectStoreName: this._name,
key: key,
- value: structuredEncapsulate(value),
+ value,
storeLevel: overwrite
? StoreLevel.AllowOverwrite
: StoreLevel.NoOverwrite,
@@ -1771,7 +1769,7 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
if (values.length !== 1) {
throw Error("invariant violated");
}
- return structuredRevive(values[0]);
+ return values[0];
};
return this._transaction._execRequestAsync({
@@ -1830,7 +1828,7 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
if (!values) {
throw Error("invariant violated");
}
- return values.map((x) => structuredRevive(x));
+ return values;
};
return this._transaction._execRequestAsync({
@@ -1891,7 +1889,7 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
if (primaryKeys.length !== 1) {
throw Error("invariant violated");
}
- return structuredRevive(primaryKeys[0]);
+ return primaryKeys[0];
};
return this._transaction._execRequestAsync({
@@ -1956,7 +1954,7 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
if (!primaryKeys) {
throw Error("invariant violated");
}
- return primaryKeys.map((x) => structuredRevive(x));
+ return primaryKeys;
};
return this._transaction._execRequestAsync({
diff --git a/packages/idb-bridge/src/idb-wpt-ported/idbindex-openCursor.test.ts b/packages/idb-bridge/src/idb-wpt-ported/idbindex-openCursor.test.ts
index 2dcab6034..f4515b69e 100644
--- a/packages/idb-bridge/src/idb-wpt-ported/idbindex-openCursor.test.ts
+++ b/packages/idb-bridge/src/idb-wpt-ported/idbindex-openCursor.test.ts
@@ -56,25 +56,27 @@ test.cb("WPT test idbindex-openCursor2.htm", (t) => {
};
});
-
// IDBIndex.openCursor() - throw InvalidStateError on index deleted by aborted upgrade
test.cb("WPT test idbindex-openCursor3.htm", (t) => {
var db;
-var open_rq = createdb(t);
-open_rq.onupgradeneeded = function(e: any) {
- db = e.target.result;
- var store = db.createObjectStore("store", { keyPath: "key" });
- var index = store.createIndex("index", "indexedProperty");
- store.add({ key: 1, indexedProperty: "data" });
+ var open_rq = createdb(t);
+ open_rq.onupgradeneeded = function (e: any) {
+ db = e.target.result;
+ var store = db.createObjectStore("store", { keyPath: "key" });
+ var index = store.createIndex("index", "indexedProperty");
+ store.add({ key: 1, indexedProperty: "data" });
- e.target.transaction.abort();
+ e.target.transaction.abort();
- t.throws(() => {
- console.log("index before openCursor", index);
- index.openCursor();
- }, { name: "InvalidStateError"});
+ t.throws(
+ () => {
+ console.log("index before openCursor", index);
+ index.openCursor();
+ },
+ { name: "InvalidStateError" },
+ );
- t.end();
-}
+ t.end();
+ };
});
diff --git a/packages/idb-bridge/src/util/canInjectKey.ts b/packages/idb-bridge/src/util/canInjectKey.ts
deleted file mode 100644
index 09ecbd3ad..000000000
--- a/packages/idb-bridge/src/util/canInjectKey.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- Copyright 2017 Jeremy Scheff
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- or implied. See the License for the specific language governing
- permissions and limitations under the License.
-*/
-
-import { IDBKeyPath } from "../idbtypes";
-
-// http://w3c.github.io/IndexedDB/#check-that-a-key-could-be-injected-into-a-value
-const canInjectKey = (keyPath: IDBKeyPath, value: any) => {
- if (Array.isArray(keyPath)) {
- // tslint:disable-next-line max-line-length
- throw new Error(
- "The key paths used in this section are always strings and never sequences, since it is not possible to create a object store which has a key generator and also has a key path that is a sequence.",
- );
- }
-
- const identifiers = keyPath.split(".");
- if (identifiers.length === 0) {
- throw new Error("Assert: identifiers is not empty");
- }
- identifiers.pop();
-
- for (const identifier of identifiers) {
- if (typeof value !== "object" && !Array.isArray(value)) {
- return false;
- }
-
- const hop = value.hasOwnProperty(identifier);
- if (!hop) {
- return true;
- }
-
- value = value[identifier];
- }
-
- return typeof value === "object" || Array.isArray(value);
-};
-
-export default canInjectKey;
diff --git a/packages/idb-bridge/src/util/deepEquals.ts b/packages/idb-bridge/src/util/deepEquals.ts
deleted file mode 100644
index bb7c0269c..000000000
--- a/packages/idb-bridge/src/util/deepEquals.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
-Copyright (c) 2017 Evgeny Poberezkin
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-*/
-
-const isArray = Array.isArray;
-const keyList = Object.keys;
-const hasProp = Object.prototype.hasOwnProperty;
-
-export function deepEquals(a: any, b: any): boolean {
- if (a === b) return true;
-
- if (a && b && typeof a == "object" && typeof b == "object") {
- const arrA = isArray(a);
- const arrB = isArray(b);
- let i;
- let length;
- let key;
-
- if (arrA && arrB) {
- length = a.length;
- if (length != b.length) return false;
- for (i = length; i-- !== 0; ) if (!deepEquals(a[i], b[i])) return false;
- return true;
- }
-
- if (arrA != arrB) return false;
-
- const dateA = a instanceof Date;
- const dateB = b instanceof Date;
- if (dateA != dateB) return false;
- if (dateA && dateB) return a.getTime() == b.getTime();
-
- const regexpA = a instanceof RegExp;
- const regexpB = b instanceof RegExp;
- if (regexpA != regexpB) return false;
- if (regexpA && regexpB) return a.toString() == b.toString();
-
- const keys = keyList(a);
- length = keys.length;
-
- if (length !== keyList(b).length) return false;
-
- for (i = length; i-- !== 0; ) if (!hasProp.call(b, keys[i])) return false;
-
- for (i = length; i-- !== 0; ) {
- key = keys[i];
- if (!deepEquals(a[key], b[key])) return false;
- }
-
- return true;
- }
-
- return a !== a && b !== b;
-}
diff --git a/packages/idb-bridge/src/util/injectKey.ts b/packages/idb-bridge/src/util/injectKey.ts
deleted file mode 100644
index 02acfaa4c..000000000
--- a/packages/idb-bridge/src/util/injectKey.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- Copyright 2017 Jeremy Scheff
- Copyright 2019 Florian Dold
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- or implied. See the License for the specific language governing
- permissions and limitations under the License.
-*/
-
-import { IDBKeyPath, IDBValidKey } from "../idbtypes";
-import { structuredClone } from "./structuredClone";
-
-export function injectKey(
- keyPath: IDBKeyPath | IDBKeyPath[],
- value: any,
- key: IDBValidKey,
-): any {
- if (Array.isArray(keyPath)) {
- // tslint:disable-next-line max-line-length
- throw new Error(
- "The key paths used in this section are always strings and never sequences, since it is not possible to create a object store which has a key generator and also has a key path that is a sequence.",
- );
- }
-
- const newValue = structuredClone(value);
-
- // Position inside the new value where we'll place the key eventually.
- let ptr = newValue;
-
- const identifiers = keyPath.split(".");
- if (identifiers.length === 0) {
- throw new Error("Assert: identifiers is not empty");
- }
-
- const lastIdentifier = identifiers.pop();
-
- if (lastIdentifier === null || lastIdentifier === undefined) {
- throw Error();
- }
-
- for (const identifier of identifiers) {
- if (typeof ptr !== "object" && !Array.isArray(ptr)) {
- throw new Error("can't inject key");
- }
-
- const hop = value.hasOwnProperty(identifier);
- if (!hop) {
- ptr[identifier] = {};
- }
-
- ptr = ptr[identifier];
- }
-
- if (!(typeof ptr === "object" || Array.isArray(ptr))) {
- throw new Error("can't inject key");
- }
-
- ptr[lastIdentifier] = structuredClone(key);
-
- return newValue;
-}
diff --git a/packages/idb-bridge/src/util/makeStoreKeyValue.ts b/packages/idb-bridge/src/util/makeStoreKeyValue.ts
index 442a69ff5..243e46e04 100644
--- a/packages/idb-bridge/src/util/makeStoreKeyValue.ts
+++ b/packages/idb-bridge/src/util/makeStoreKeyValue.ts
@@ -18,7 +18,6 @@ import { extractKey } from "./extractKey";
import { DataError } from "./errors";
import { valueToKey } from "./valueToKey";
import { structuredClone } from "./structuredClone";
-import { injectKey } from "./injectKey";
import { IDBKeyPath, IDBValidKey } from "../idbtypes";
export interface StoreKeyResult {
@@ -27,6 +26,55 @@ export interface StoreKeyResult {
value: any;
}
+export function injectKey(
+ keyPath: IDBKeyPath | IDBKeyPath[],
+ value: any,
+ key: IDBValidKey,
+): any {
+ if (Array.isArray(keyPath)) {
+ throw new Error(
+ "The key paths used in this section are always strings and never sequences, since it is not possible to create a object store which has a key generator and also has a key path that is a sequence.",
+ );
+ }
+
+ const newValue = structuredClone(value);
+
+ // Position inside the new value where we'll place the key eventually.
+ let ptr = newValue;
+
+ const identifiers = keyPath.split(".");
+ if (identifiers.length === 0) {
+ throw new Error("Assert: identifiers is not empty");
+ }
+
+ const lastIdentifier = identifiers.pop();
+
+ if (lastIdentifier === null || lastIdentifier === undefined) {
+ throw Error();
+ }
+
+ for (const identifier of identifiers) {
+ if (typeof ptr !== "object" && !Array.isArray(ptr)) {
+ throw new Error("can't inject key");
+ }
+
+ const hop = value.hasOwnProperty(identifier);
+ if (!hop) {
+ ptr[identifier] = {};
+ }
+
+ ptr = ptr[identifier];
+ }
+
+ if (!(typeof ptr === "object" || Array.isArray(ptr))) {
+ throw new Error("can't inject key");
+ }
+
+ ptr[lastIdentifier] = structuredClone(key);
+
+ return newValue;
+}
+
export function makeStoreKeyValue(
value: any,
key: IDBValidKey | undefined,
diff --git a/packages/idb-bridge/src/util/structuredClone.test.ts b/packages/idb-bridge/src/util/structuredClone.test.ts
new file mode 100644
index 000000000..58a7f32c1
--- /dev/null
+++ b/packages/idb-bridge/src/util/structuredClone.test.ts
@@ -0,0 +1,39 @@
+/*
+ Copyright 2019 Florian Dold
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ or implied. See the License for the specific language governing
+ permissions and limitations under the License.
+*/
+
+import test, { ExecutionContext } from "ava";
+import { structuredClone } from "./structuredClone";
+
+function checkClone(t: ExecutionContext, x: any): void {
+ t.deepEqual(structuredClone(x), x);
+}
+
+test("structured clone", (t) => {
+ checkClone(t, "foo");
+ checkClone(t, [1, 2]);
+ checkClone(t, { x1: "foo" });
+ checkClone(t, new Date());
+ checkClone(t, [new Date()]);
+ checkClone(t, undefined);
+ checkClone(t, [undefined]);
+});
+
+test("structured clone (cycles)", (t) => {
+ const obj1: any[] = [1, 2];
+ obj1.push(obj1);
+ const obj1Clone = structuredClone(obj1);
+ t.is(obj1Clone, obj1Clone[2]);
+});
diff --git a/packages/idb-bridge/src/util/structuredClone.ts b/packages/idb-bridge/src/util/structuredClone.ts
index 215681a2d..4ba97dd7a 100644
--- a/packages/idb-bridge/src/util/structuredClone.ts
+++ b/packages/idb-bridge/src/util/structuredClone.ts
@@ -14,8 +14,6 @@
permissions and limitations under the License.
*/
-import { DataCloneError } from "./errors";
-
const { toString: toStr } = {};
const hasOwn = {}.hasOwnProperty;
const getProto = Object.getPrototypeOf;
@@ -46,11 +44,6 @@ function hasConstructorOf(a: any, b: any) {
return false;
}
-/**
- *
- * @param {any} val
- * @returns {boolean}
- */
function isPlainObject(val: any): boolean {
if (!val || toStringTag(val) !== "Object") {
return false;
@@ -157,11 +150,7 @@ export function structuredEncapsulate(val: any): any {
const outRoot = {};
const types: Array<[string[], string]> = [];
let res;
- try {
- res = internalEncapsulate(val, outRoot, [], new Map(), types);
- } catch (e) {
- throw new DataCloneError();
- }
+ res = internalEncapsulate(val, outRoot, [], new Map(), types);
if (res === null) {
return res;
}
@@ -218,32 +207,50 @@ export function internalStructuredRevive(val: any): any {
const last = path[path.length - 1];
obj[last] = f(obj[last]);
}
+ function lookupPath(path: string[]): any {
+ let obj = outRoot;
+ for (const n of path) {
+ obj = obj[n];
+ }
+ return obj;
+ }
for (const [path, type] of types) {
- if (type === "bigint") {
- mutatePath(path, (x) => BigInt(x));
- } else if (type === "array") {
- mutatePath(path, (x) => {
- const newArr: any = [];
- for (const k in x) {
- newArr[k] = x[k];
- }
- return newArr;
- });
- } else if (type === "date") {
- mutatePath(path, (x) => new Date(x));
- } else {
- throw Error("type not implemented");
+ switch (type) {
+ case "bigint": {
+ mutatePath(path, (x) => BigInt(x));
+ break;
+ }
+ case "array": {
+ mutatePath(path, (x) => {
+ const newArr: any = [];
+ for (const k in x) {
+ newArr[k] = x[k];
+ }
+ return newArr;
+ });
+ break;
+ }
+ case "date": {
+ mutatePath(path, (x) => new Date(x));
+ break;
+ }
+ case "undef": {
+ mutatePath(path, (x) => undefined);
+ break;
+ }
+ case "ref": {
+ mutatePath(path, (x) => lookupPath(x));
+ break;
+ }
+ default:
+ throw Error(`type '${type}' not implemented`);
}
}
return outRoot;
}
export function structuredRevive(val: any): any {
- try {
- return internalStructuredRevive(val);
- } catch (e) {
- throw new DataCloneError();
- }
+ return internalStructuredRevive(val);
}
/**