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
  • Loading branch information
calixteman committed Dec 5, 2020
1 parent 54ca67d commit 3358fe2
Show file tree
Hide file tree
Showing 15 changed files with 287 additions and 194 deletions.
51 changes: 25 additions & 26 deletions external/quickjs/quickjs-eval.js

Large diffs are not rendered by default.

15 changes: 14 additions & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,19 @@ function createScriptingBundle(defines) {
.src("./src/pdf.scripting.js")
.pipe(webpack2Stream(scriptingFileConfig))
.pipe(replaceWebpackRequire())
.pipe(replaceJSRootName(scriptingAMDName, "pdfjsScripting"));
.pipe(
replace(
'root["' + scriptingAMDName + '"] = factory()',
"root.pdfjsScripting = factory()"
)
);
}

function copyScriptingExtra() {
return gulp
.src("./src/scripting_api/extra.js")
.pipe(rename("pdf.scripting.extra.js"))
.pipe(replace("export { buildExtra };", ""));
}

function createSandboxBundle(defines, code) {
Expand Down Expand Up @@ -1195,6 +1207,7 @@ gulp.task(
createScriptingBundle(defines).pipe(
gulp.dest(MOZCENTRAL_CONTENT_DIR + "build")
),
copyScriptingExtra().pipe(gulp.dest(MOZCENTRAL_CONTENT_DIR + "build")),
createWorkerBundle(defines).pipe(
gulp.dest(MOZCENTRAL_CONTENT_DIR + "build")
),
Expand Down
32 changes: 12 additions & 20 deletions src/scripting_api/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,9 @@ class App extends PDFObject {
data.calculationOrder,
this._objects
);
this._setTimeout = data.setTimeout;
this._clearTimeout = data.clearTimeout;
this._setInterval = data.setInterval;
this._clearInterval = data.clearInterval;
this._timeoutIds = null;
this._timeoutIdsRegistry = null;

// used in proxy.js to check that this is the object with the backdoor
this._isApp = true;
this._extra = data.extra;
}

// This function is called thanks to the proxy
Expand Down Expand Up @@ -90,7 +84,7 @@ class App extends PDFObject {
);
}
}
this._timeoutIds.set(timeout, [id, interval]);
this._timeoutIds.set(timeout, id);
if (this._timeoutIdsRegistry) {
this._timeoutIdsRegistry.register(timeout, [id, interval]);
}
Expand All @@ -100,16 +94,16 @@ class App extends PDFObject {
if (!this._timeoutIds || !this._timeoutIds.has(timeout)) {
return;
}
const [id, interval] = this._timeoutIds.get(timeout);
const { id, interval } = this._timeoutIds.get(timeout);
if (this._timeoutIdsRegistry) {
this._timeoutIdsRegistry.unregister(timeout);
}
this._timeoutIds.delete(timeout);

if (interval) {
this._clearInterval(id);
this._extra.clearInterval(id);
} else {
this._clearTimeout(id);
this._extra.clearTimeout(id);
}
}

Expand Down Expand Up @@ -409,7 +403,7 @@ class App extends PDFObject {
oDoc = null,
oCheckbox = null
) {
this._send({ command: "alert", value: cMsg });
this._extra.alert(cMsg);
}

beep() {
Expand All @@ -425,11 +419,11 @@ class App extends PDFObject {
}

clearInterval(oInterval) {
this.unregisterTimeout(oInterval);
this._unregisterTimeout(oInterval);
}

clearTimeOut(oTime) {
this.unregisterTimeout(oTime);
this._unregisterTimeout(oTime);
}

endPriv() {
Expand Down Expand Up @@ -524,8 +518,8 @@ class App extends PDFObject {
/* Not implemented */
}

response() {
/* TODO or not */
response(cQuestion, cTitle = "", cDefault = "", bPassword = "", cLabel = "") {
return this._extra.prompt(cQuestion, cDefault);
}

setInterval(cExpr, nMilliseconds) {
Expand All @@ -537,8 +531,7 @@ class App extends PDFObject {
"Second argument of app.setInterval must be a number"
);
}

const id = this._setInterval(cExpr, nMilliseconds);
const id = this._extra.setInterval(cExpr, nMilliseconds);
const timeout = Object.create(null);
this._registerTimeout(timeout, id, true);
return timeout;
Expand All @@ -551,8 +544,7 @@ class App extends PDFObject {
if (typeof nMilliseconds !== "number") {
throw new TypeError("Second argument of app.setTimeOut must be a number");
}

const id = this._setTimeout(cExpr, nMilliseconds);
const id = this._extra.setTimeout(cExpr, nMilliseconds);
const timeout = Object.create(null);
this._registerTimeout(timeout, id, false);
return timeout;
Expand Down
1 change: 0 additions & 1 deletion src/scripting_api/doc.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ class InfoProxyHandler {
class Doc extends PDFObject {
constructor(data) {
super(data);
this.calculate = true;

this.baseURL = data.baseURL || "";
this.calculate = true;
Expand Down
73 changes: 73 additions & 0 deletions src/scripting_api/extra.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/* 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.
*/

function buildExtra(win, evalObj) {
const timeoutIds = new Set();
return {
setTimeout(cExpr, nMilliseconds) {
if (typeof cExpr !== "string" || typeof nMilliseconds !== "number") {
return null;
}
const id = win.setTimeout(() => {
timeoutIds.delete(id);
try {
evalObj.call(cExpr);
} catch (_) {}
}, nMilliseconds);
timeoutIds.add(id);

return id;
},
clearTimeout(id) {
if (timeoutIds.has(id)) {
win.clearTimeout(id);
timeoutIds.delete(id);
}
},
setInterval(cExpr, nMilliseconds) {
if (typeof cExpr !== "string" || typeof nMilliseconds !== "number") {
return null;
}
const id = win.setInterval(() => {
try {
evalObj.call(cExpr);
} catch (_) {}
}, nMilliseconds);
timeoutIds.add(id);

return id;
},
clearInterval(id) {
if (timeoutIds.has(id)) {
win.clearInterval(id);
timeoutIds.delete(id);
}
},
alert(cMsg) {
if (typeof cMsg !== "string") {
return;
}
win.alert(cMsg);
},
prompt(cQuestion, cDefault) {
if (typeof cQuestion !== "string" || typeof cDefault !== "string") {
return "";
}
return win.prompt(cQuestion, cDefault) || "";
},
};
}

export { buildExtra };
24 changes: 9 additions & 15 deletions src/scripting_api/initialization.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,33 +35,25 @@ import { Field } from "./field.js";
import { ProxyHandler } from "./proxy.js";
import { Util } from "./util.js";

function initSandbox({ data, extra, out }) {
const proxyHandler = new ProxyHandler(data.dispatchEventName);
const {
send,
crackURL,
setTimeout,
clearTimeout,
setInterval,
clearInterval,
} = extra;
function initSandbox(params) {
const { data, extra, out } = params;
const proxyHandler = new ProxyHandler();
const { send } = extra;
const doc = new Doc({
send,
...data.docInfo,
});
const _document = { obj: doc, wrapped: new Proxy(doc, proxyHandler) };
const app = new App({
send,
setTimeout,
clearTimeout,
setInterval,
clearInterval,
extra,
_document,
calculationOrder: data.calculationOrder,
proxyHandler,
...data.appInfo,
});
const util = new Util({ crackURL });

const util = new Util();
const aform = new AForm(doc, app, util);

for (const [name, objs] of Object.entries(data.objects)) {
Expand Down Expand Up @@ -96,6 +88,8 @@ function initSandbox({ data, extra, out }) {
out[name] = aform[name].bind(aform);
}
}

return app._dispatchEvent.bind(app);
}

export { initSandbox };
11 changes: 0 additions & 11 deletions src/scripting_api/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,7 @@
*/

class ProxyHandler {
constructor(dispatchEventName) {
this.dispatchEventName = dispatchEventName;
}

get(obj, prop) {
if (obj._isApp && prop === this.dispatchEventName) {
// a backdoor to be able to call _dispatchEvent method
// the value of 'dispatchEvent' is generated randomly
// and injected in the code
return obj._dispatchEvent.bind(obj);
}

// script may add some properties to the object
if (prop in obj._expandos) {
const val = obj._expandos[prop];
Expand Down
71 changes: 39 additions & 32 deletions src/scripting_api/quickjs-sandbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,70 +13,70 @@
* limitations under the License.
*/

import { buildExtra } from "./extra.js";
import ModuleLoader from "../../external/quickjs/quickjs-eval.js";

class Sandbox {
constructor(module, testMode) {
constructor(module, testMode, extraNames) {
this._evalInSandbox = module.cwrap("evalInSandbox", null, [
"string",
"int",
]);
this._dispatchEventName = null;
this._dispatchEvent = module.cwrap("dispatchEvent", null, [
"string",
"int",
]);
this._module = module;
this._testMode = testMode;
this._alertOnError = 1;
this._alertOnError = testMode ? 1 : 0;
this._extraNames = extraNames;
}

create(data) {
const sandboxData = JSON.stringify(data);
const extra = [
"send",
"setTimeout",
"clearTimeout",
"setInterval",
"clearInterval",
"crackURL",
];
const extra = ["send", "parseURL"].concat(this._extraNames);
delete this._extraNames;
const extraStr = extra.join(",");
let code = [
"exports = Object.create(null);",
"module = Object.create(null);",
const code = [
// Next line is replaced by code from initialization.js
// when we create the bundle for the sandbox.
"/* INITIALIZATION_CODE */",
`data = ${sandboxData};`,
`module.exports.initSandbox({ data, extra: {${extraStr}}, out: this});`,
"delete exports;",
"delete module;",
"delete data;",
`pdfjsScripting.initSandbox({ data: ${sandboxData}, extra: {${extraStr}}, out: this})`,
];
let cleanup = ["delete pdfjsScripting;"];
if (!this._testMode) {
code = code.concat(extra.map(name => `delete ${name};`));
code.push("delete debugMe;");
cleanup = cleanup.concat(extra.map(name => `delete ${name};`));
cleanup.push("delete debugMe;");
}
const success = !!this._module.ccall(
"initSandbox",
"int",
["string", "string", "int"],
[code.join("\n"), cleanup.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");
if (this._dispatchEvent !== null) {
try {
event = JSON.stringify(event);
this._dispatchEvent(event, this._alertOnError);
} catch (_) {}
}
event = JSON.stringify(event);
this._evalInSandbox(
`app["${this._dispatchEventName}"](${event});`,
this._alertOnError
);
}

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

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

Expand All @@ -99,8 +99,15 @@ function QuickJSSandbox(testMode = false) {
testMode &&
(typeof PDFJSDev === "undefined" ||
PDFJSDev.test("!PRODUCTION || TESTING"));

const evalObj = { call: s => {} };
window.sandboxExtra = buildExtra(window, evalObj);
const extraNames = Object.getOwnPropertyNames(window.sandboxExtra);

return ModuleLoader().then(module => {
return new Sandbox(module, testMode);
const sbx = new Sandbox(module, testMode, extraNames);
evalObj.call = sbx._evalInSandbox.bind(sbx);
return sbx;
});
}

Expand Down
Loading

0 comments on commit 3358fe2

Please sign in to comment.