diff --git a/spec/common/providers/https.spec.ts b/spec/common/providers/https.spec.ts index 55f698575..9de4fb234 100644 --- a/spec/common/providers/https.spec.ts +++ b/spec/common/providers/https.spec.ts @@ -949,6 +949,21 @@ describe("encoding/decoding", () => { }); }); + it("Throws object with self reference", () => { + class TestClass { + foo: string; + bar: number; + self: TestClass; + constructor(foo: string, bar: number) { + this.foo = foo; + this.bar = bar; + this.self = this; + } + } + const testObject = new TestClass("hello", 1); + expect(()=>https.encode(testObject)).to.throw(`Data cannot be encoded in JSON: ${testObject}`); + }); + it("encodes function as an empty object", () => { expect(https.encode(() => "foo")).to.deep.equal({}); }); diff --git a/src/common/providers/https.ts b/src/common/providers/https.ts index d798a2579..72a4201b3 100644 --- a/src/common/providers/https.ts +++ b/src/common/providers/https.ts @@ -404,12 +404,14 @@ const LONG_TYPE = "type.googleapis.com/google.protobuf.Int64Value"; /** @hidden */ const UNSIGNED_LONG_TYPE = "type.googleapis.com/google.protobuf.UInt64Value"; +/** @hidden */ +const SELF_REFERENCE_WEAKSET = new WeakSetany)>(); /** * Encodes arbitrary data in our special format for JSON. * This is exposed only for testing. */ /** @hidden */ -export function encode(data: any): any { +export function encode(data: unknown): any { if (data === null || typeof data === "undefined") { return null; } @@ -430,18 +432,36 @@ export function encode(data: any): any { if (Array.isArray(data)) { return data.map(encode); } - if (typeof data === "object" || typeof data === "function") { - // Sadly we don't have Object.fromEntries in Node 10, so we can't use a single - // list comprehension - const obj: Record = {}; - for (const [k, v] of Object.entries(data)) { - obj[k] = encode(v); - } - return obj; + if(!isFunctionOrObject(data)||SELF_REFERENCE_WEAKSET.has(data)) { + logger.error("Data cannot be encoded in JSON.", data); + throw new Error(`Data cannot be encoded in JSON: ${data}`); + } + + // Sadly we don't have Object.fromEntries in Node 10, so we can't use a single + // list comprehension + const obj: Record = {}; + + SELF_REFERENCE_WEAKSET.add(data); + + for (const [k, v] of Object.entries(data)) { + obj[k] = encode(v); } - // If we got this far, the data is not encodable. - logger.error("Data cannot be encoded in JSON.", data); - throw new Error(`Data cannot be encoded in JSON: ${data}`); + + // clean after recursive call - + // we don't want to keep references to objects that are not part of the current object + SELF_REFERENCE_WEAKSET.delete(data); + + return obj; +} + +function isFunctionOrObject(data: unknown): data is object|((...args:any[])=>any) { + const isObjectOrFunction = typeof data === "object" || typeof data === "function"; + + if (!isObjectOrFunction) { + // If we got this far, the data is not encodable. + return false; + } + return true; } /**