From 48c0798a2b73e73adcbb16e70ebfd837f4552b51 Mon Sep 17 00:00:00 2001 From: Andrew Swan Date: Thu, 16 Jun 2016 08:30:58 -0700 Subject: [PATCH] Bug 1274708 Add BaseContext.jsonStringify() r=kmag MozReview-Commit-ID: E4F1e8hDA5a --- .../components/extensions/ExtensionUtils.jsm | 19 ++++++ .../test/xpcshell/test_ext_contexts.js | 61 ++++++++++++++++++- 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/toolkit/components/extensions/ExtensionUtils.jsm b/toolkit/components/extensions/ExtensionUtils.jsm index 8f691ee378e5..6eb36e93ae0b 100644 --- a/toolkit/components/extensions/ExtensionUtils.jsm +++ b/toolkit/components/extensions/ExtensionUtils.jsm @@ -148,6 +148,7 @@ class BaseContext { this.contextId = ++gContextId; this.unloaded = false; this.extensionId = extensionId; + this.jsonSandbox = null; } get cloneScope() { @@ -196,6 +197,24 @@ class BaseContext { return true; } + /** + * Safely call JSON.stringify() on an object that comes from an + * extension. + * + * @param {array} args Arguments for JSON.stringify() + * @returns {string} The stringified representation of obj + */ + jsonStringify(...args) { + if (!this.jsonSandbox) { + this.jsonSandbox = Cu.Sandbox(this.principal, { + sameZoneAs: this.cloneScope, + wantXrays: false, + }); + } + + return Cu.waiveXrays(this.jsonSandbox.JSON).stringify(...args); + } + callOnClose(obj) { this.onClose.add(obj); } diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js b/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js index 1a75ceb4fe06..c54638530ba4 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js @@ -14,7 +14,7 @@ var { class StubContext extends BaseContext { constructor() { super(); - this.sandbox = new Cu.Sandbox(global); + this.sandbox = Cu.Sandbox(global); } get cloneScope() { @@ -127,3 +127,62 @@ add_task(function* test_post_unload_listeners() { // any micro-tasks that get enqueued by the resolution handlers above. yield new Promise(resolve => setTimeout(resolve, 0)); }); + +class Context extends BaseContext { + constructor(principal) { + super(); + Object.defineProperty(this, "principal", { + value: principal, + configurable: true, + }); + this.sandbox = Cu.Sandbox(principal, {wantXrays: false}); + this.extension = {id: "test@web.extension"}; + } + + get cloneScope() { + return this.sandbox; + } +} + +let ssm = Services.scriptSecurityManager; +const PRINCIPAL1 = ssm.createCodebasePrincipalFromOrigin("http://www.example.org"); +const PRINCIPAL2 = ssm.createCodebasePrincipalFromOrigin("http://www.somethingelse.org"); + +// Test that toJSON() works in the json sandbox +add_task(function* test_stringify_toJSON() { + let context = new Context(PRINCIPAL1); + let obj = Cu.evalInSandbox("({hidden: true, toJSON() { return {visible: true}; } })", context.sandbox); + + let stringified = context.jsonStringify(obj); + let expected = JSON.stringify({visible: true}); + equal(stringified, expected, "Stringified object with toJSON() method is as expected"); +}); + +// Test that stringifying in inaccessible property throws +add_task(function* test_stringify_inaccessible() { + let context = new Context(PRINCIPAL1); + let sandbox = context.sandbox; + let sandbox2 = Cu.Sandbox(PRINCIPAL2); + + Cu.waiveXrays(sandbox).subobj = Cu.evalInSandbox("({ subobject: true })", sandbox2); + let obj = Cu.evalInSandbox("({ local: true, nested: subobj })", sandbox); + Assert.throws(() => { + context.jsonStringify(obj); + }); +}); + +add_task(function* test_stringify_accessible() { + // Test that an accessible property from another global is included + let principal = ssm.createExpandedPrincipal([PRINCIPAL1, PRINCIPAL2]); + let context = new Context(principal); + let sandbox = context.sandbox; + let sandbox2 = Cu.Sandbox(PRINCIPAL2); + + Cu.waiveXrays(sandbox).subobj = Cu.evalInSandbox("({ subobject: true })", sandbox2); + let obj = Cu.evalInSandbox("({ local: true, nested: subobj })", sandbox); + let stringified = context.jsonStringify(obj); + + let expected = JSON.stringify({local: true, nested: {subobject: true}}); + equal(stringified, expected, "Stringified object with accessible property is as expected"); +}); +