Skip to content

Commit

Permalink
In order to simplify m-c code, move some in pdf.js
Browse files Browse the repository at this point in the history
 * move set/clear|Timeout/Interval and crackURL code in pdf.js
 * remove the "backdoor" in the proxy (used to dispatch event) and so return the dispatch function in the initializer
 * remove listeners if an error occured during sandbox initialization
 * add support for alert and prompt in the sandbox
 * add a function to eval in the global scope
  • Loading branch information
calixteman committed Dec 14, 2020
1 parent 00b4f86 commit 1504cc9
Show file tree
Hide file tree
Showing 15 changed files with 379 additions and 247 deletions.
50 changes: 24 additions & 26 deletions external/quickjs/quickjs-eval.js

Large diffs are not rendered by default.

28 changes: 26 additions & 2 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,28 @@ function createScriptingBundle(defines, extraOptions = undefined) {
.src("./src/pdf.scripting.js")
.pipe(webpack2Stream(scriptingFileConfig))
.pipe(replaceWebpackRequire())
.pipe(replaceJSRootName(scriptingAMDName, "pdfjsScripting"));
.pipe(
replace(
'root["' + scriptingAMDName + '"] = factory()',
"root.pdfjsScripting = factory()"
)
);
}

function createSandboxExternal(defines) {
const preprocessor2 = require("./external/builder/preprocessor2.js");
const licenseHeader = fs.readFileSync("./src/license_header.js").toString();

const ctx = {
saveComments: false,
defines,
};
return gulp.src("./src/pdf.sandbox.external.js").pipe(
transform("utf8", content => {
content = preprocessor2.preprocessPDFJSCode(ctx, content);
return `${licenseHeader}\n${content}`;
})
);
}

function createTemporaryScriptingBundle(defines, extraOptions = undefined) {
Expand Down Expand Up @@ -1204,6 +1225,9 @@ gulp.task(
createScriptingBundle(defines).pipe(
gulp.dest(MOZCENTRAL_CONTENT_DIR + "build")
),
createSandboxExternal(defines).pipe(
gulp.dest(MOZCENTRAL_CONTENT_DIR + "build")
),
createWorkerBundle(defines).pipe(
gulp.dest(MOZCENTRAL_CONTENT_DIR + "build")
),
Expand Down Expand Up @@ -1789,7 +1813,7 @@ gulp.task(
gulp.task("watch-dev-sandbox", function () {
gulp.watch(
[
"src/pdf.{sandbox,scripting}.js",
"src/pdf.{sandbox,sandbox.external,scripting}.js",
"src/scripting_api/*.js",
"src/shared/scripting_utils.js",
"external/quickjs/*.js",
Expand Down
107 changes: 107 additions & 0 deletions src/pdf.sandbox.external.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/* Copyright 2020 Mozilla Foundation
*
* 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.
*/

// In mozilla-central, this file is loaded as non-module script,
// so it mustn't have any dependencies.

function buildExternals(win, sandbox) {
const timeoutIds = new Map();
return {
setTimeout(callbackId, nMilliseconds) {
if (typeof callbackId !== "number" || typeof nMilliseconds !== "number") {
return;
}
const id = win.setTimeout(() => {
timeoutIds.delete(callbackId);
sandbox.callFunction("timeoutCb", { callbackId, interval: false });
}, nMilliseconds);
timeoutIds.set(callbackId, id);
},
clearTimeout(id) {
win.clearTimeout(timeoutIds.get(id));
timeoutIds.delete(id);
},
setInterval(callbackId, nMilliseconds) {
if (typeof callbackId !== "number" || typeof nMilliseconds !== "number") {
return;
}
const id = win.setInterval(() => {
sandbox.callFunction("timeoutCb", { callbackId, interval: true });
}, nMilliseconds);
timeoutIds.set(callbackId, id);
},
clearInterval(id) {
win.clearInterval(timeoutIds.get(id));
timeoutIds.delete(id);
},
cleanTimeouts() {
timeoutIds.forEach(([_, id]) => win.clearTimeout(id));
timeoutIds.clear();
},
alert(cMsg) {
if (typeof cMsg !== "string") {
return;
}
win.alert(cMsg);
},
prompt(cQuestion, cDefault) {
if (typeof cQuestion !== "string" || typeof cDefault !== "string") {
return null;
}
return win.prompt(cQuestion, cDefault);
},
parseURL(cUrl) {
let ret;
try {
const url = new win.URL(cUrl);
const props = [
"hash",
"host",
"hostname",
"href",
"origin",
"password",
"pathname",
"port",
"protocol",
"search",
"searchParams",
"username",
];

ret = Object.fromEntries(
props.map(name => [name, url[name].toString()])
);
} catch (err) {
ret = { error: err.message };
}
return sandbox.cloneIn(ret);
},
send(data) {
try {
if (!data) {
return;
}
const detail = sandbox.cloneOut(data, win);
const event = new win.CustomEvent("updateFromSandbox", { detail });
win.dispatchEvent(event);
} catch (_) {}
},
};
}

if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
exports.buildExternals = buildExternals;
}
129 changes: 84 additions & 45 deletions src/pdf.sandbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* limitations under the License.
*/

import { buildExternals } from "./pdf.sandbox.external.js";
import ModuleLoader from "../external/quickjs/quickjs-eval.js";

/* eslint-disable-next-line no-unused-vars */
Expand All @@ -24,86 +25,124 @@ const TESTING =
typeof PDFJSDev === "undefined" || PDFJSDev.test("!PRODUCTION || TESTING");

class Sandbox {
constructor(module) {
this._evalInSandbox = module.cwrap("evalInSandbox", null, [
"string",
"int",
]);
this._dispatchEventName = null;
constructor(module, externalNames) {
this._commFun = module.cwrap("commFun", null, ["string", "string"]);
this._module = module;
this._alertOnError = 1;
this._alertOnError = 0;
this._externalNames = externalNames;
}

create(data) {
const sandboxData = JSON.stringify(data);
const extra = [
"send",
"setTimeout",
"clearTimeout",
"setInterval",
"clearInterval",
"crackURL",
];
const extraStr = extra.join(",");
let code = [
"exports = Object.create(null);",
"module = Object.create(null);",
const sandboxData = Sandbox.cloneIn(data);
// In the sandox, external functions like send, setTimeout, ... exist
// as global variables.
const external = `{ ${this._externalNames.join(",")} }`;
const code = [
// Next line is replaced by code from initialization.js
// when we create the bundle for the sandbox.
PDFJSDev.eval("PDF_SCRIPTING_JS_SOURCE"),
`data = ${sandboxData};`,
`module.exports.initSandbox({ data, extra: {${extraStr}}, out: this});`,
"delete exports;",
"delete module;",
"delete data;",
`pdfjsScripting.initSandbox({ data: ${sandboxData}, external: ${external} })`,
];
if (!TESTING) {
code = code.concat(extra.map(name => `delete ${name};`));
code.push("delete debugMe;");
// Don't expose external functions
code.push(this._externalNames.map(name => `delete ${name};`).join(";"));
delete this._externalNames;
}
const success = !!this._module.ccall(
"initSandbox",
"int",
["string", "int"],
[code.join("\n"), this._alertOnError]
);
if (!success) {
this.nukeSandbox();
}
this._evalInSandbox(code.join("\n"), this._alertOnError);
this._dispatchEventName = data.dispatchEventName;
return success;
}

dispatchEvent(event) {
if (this._dispatchEventName === null) {
throw new Error("Sandbox must have been initialized");
callFunction(name, args) {
if (this._commFun && typeof name === "string") {
try {
args = Sandbox.cloneIn(args);
this._commFun(name, args);
} catch (err) {
console.error(name, args, err);
}
}
event = JSON.stringify(event);
this._evalInSandbox(
`app["${this._dispatchEventName}"](${event});`,
this._alertOnError
);
}

dispatchEvent(event) {
this.callFunction("dispatchEvent", event);
}

dumpMemoryUse() {
this._module.ccall("dumpMemoryUse", null, []);
if (this._module) {
this._module.ccall("dumpMemoryUse", null, []);
}
}

nukeSandbox() {
this._dispatchEventName = null;
this._module.ccall("nukeSandbox", null, []);
this._module = null;
this._evalInSandbox = null;
if (this._module !== null) {
this.callFunction("finalize", null);
this._module.ccall("nukeSandbox", null, []);
this._module = null;
this._dispatchEvent = null;
}
}

static cloneIn(obj) {
// The communication with the Quickjs sandbox is based on strings
// So we use JSON.stringfy to serialize
return window.JSON.stringify(obj);
}

static cloneOut(obj) {
// ... and JSON.parse to deserialize
return window.JSON.parse(obj);
}

evalForTesting(code, key) {
if (TESTING) {
this._evalInSandbox(
`try {
this._evalInSandbox = this._module.ccall(
"evalInSandbox",
null,
["string", "int"],
[
`try {
send({ id: "${key}", result: ${code} });
} catch (error) {
send({ id: "${key}", result: error.message });
}`,
this._alertOnError
this._alertOnError,
]
);
}
}
}

function QuickJSSandbox() {
const handlers = {
callFunction(name, args) {},
cloneIn: Sandbox.cloneIn,
cloneOut: Sandbox.cloneOut,
};
window.sandboxExternals = buildExternals(window, handlers);
if (TESTING) {
const send = window.sandboxExternals.send;
window.sandboxExternals.alert = value => {
send(Sandbox.cloneIn({ command: "alert", value }));
};
}

const externalNames = Object.getOwnPropertyNames(window.sandboxExternals);

return ModuleLoader().then(module => {
return new Sandbox(module);
delete window.sandboxExternals;
const sbx = new Sandbox(module, externalNames);
handlers.callFunction = sbx.callFunction.bind(sbx);

return sbx;
});
}

Expand Down
Loading

0 comments on commit 1504cc9

Please sign in to comment.