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
  • Loading branch information
calixteman committed Dec 4, 2020
1 parent a618b02 commit b96f54d
Show file tree
Hide file tree
Showing 14 changed files with 298 additions and 196 deletions.
50 changes: 24 additions & 26 deletions external/quickjs/quickjs-eval.js

Large diffs are not rendered by default.

86 changes: 62 additions & 24 deletions src/scripting_api/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,12 @@ class App extends PDFObject {
this._eventDispatcher = new EventDispatcher(
this._document,
data.calculationOrder,
this._objects
this._objects,
this
);
this._setTimeout = data.setTimeout;
this._clearTimeout = data.clearTimeout;
this._setInterval = data.setInterval;
this._clearInterval = data.clearInterval;
this._timeoutId = 0;
this._timeoutIds = null;
this._timeoutIdsRegistry = null;

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

// This function is called thanks to the proxy
Expand All @@ -66,7 +61,36 @@ class App extends PDFObject {
this._eventDispatcher.dispatch(pdfEvent);
}

_registerTimeout(timeout, id, interval) {
_fallback(params) {
const name = params.id;
if (name === "timeoutCallback" || name === "intervalCallback") {
const data = this._timeoutIds.get(params.callbackId);
if (data) {
if (name === "timeoutCallback") {
this._unregisterTimeout(params.callbackId, false);
}
try {
data.callback();
} catch (_) {}
}
}
}

_clearTimeout(timeoutId) {
this._send({
command: "clearTimeout",
timeoutId,
});
}

_clearInterval(intervalId) {
this._send({
command: "clearInterval",
intervalId,
});
}

_registerTimeout(timeout, id, interval, code) {
if (!this._timeoutIds) {
this._timeoutIds = new WeakMap();
// FinalizationRegistry isn't implemented in QuickJS
Expand All @@ -90,26 +114,32 @@ class App extends PDFObject {
);
}
}
this._timeoutIds.set(timeout, [id, interval]);
this._timeoutIds.set(timeout, {
id,
// eslint-disable-next-line no-new-func
callback: Function(`with (this) {${code}}`).bind(this._document.doc),
});
if (this._timeoutIdsRegistry) {
this._timeoutIdsRegistry.register(timeout, [id, interval]);
}
}

_unregisterTimeout(timeout) {
_unregisterTimeout(timeout, clear) {
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);
} else {
this._clearTimeout(id);
if (clear) {
if (interval) {
this._clearInterval(id);
} else {
this._clearTimeout(id);
}
}
}

Expand Down Expand Up @@ -425,11 +455,11 @@ class App extends PDFObject {
}

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

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

endPriv() {
Expand Down Expand Up @@ -537,10 +567,14 @@ class App extends PDFObject {
"Second argument of app.setInterval must be a number"
);
}

const id = this._setInterval(cExpr, nMilliseconds);
const id = this._timeoutId++;
this._send({
command: "setInterval",
nMilliseconds,
intervalId: id,
});
const timeout = Object.create(null);
this._registerTimeout(timeout, id, true);
this._registerTimeout(timeout, id, true, cExpr);
return timeout;
}

Expand All @@ -551,10 +585,14 @@ 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._timeoutId++;
this._send({
command: "setTimeout",
nMilliseconds,
timeoutId: id,
});
const timeout = Object.create(null);
this._registerTimeout(timeout, id, false);
this._registerTimeout(timeout, id, false, cExpr);
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
4 changes: 3 additions & 1 deletion src/scripting_api/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ class Event {
}

class EventDispatcher {
constructor(document, calculationOrder, objects) {
constructor(document, calculationOrder, objects, app) {
this._document = document;
this._calculationOrder = calculationOrder;
this._objects = objects;
this._app = app;

this._document.obj._eventDispatcher = this;
}
Expand All @@ -65,6 +66,7 @@ class EventDispatcher {
dispatch(baseEvent) {
const id = baseEvent.id;
if (!(id in this._objects)) {
this._app._fallback(baseEvent);
return;
}

Expand Down
23 changes: 8 additions & 15 deletions src/scripting_api/initialization.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,33 +35,24 @@ 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,
_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 +87,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
68 changes: 34 additions & 34 deletions src/scripting_api/quickjs-sandbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,78 +17,78 @@ import ModuleLoader from "../../external/quickjs/quickjs-eval.js";

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

create(data) {
const sandboxData = JSON.stringify(data);
const extra = [
"send",
"setTimeout",
"clearTimeout",
"setInterval",
"clearInterval",
"crackURL",
];
const extra = ["send", "parseURL"];
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;",
`delete this["pdfjs-dist/build/pdf.scripting"];`,
];
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._evalInSandbox = null;
this._dispatchEvent = null;
}

evalForTesting(code, key) {
if (this._testMode) {
this._evalInSandbox(
`try {
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,
]
);
}
}
Expand Down
Loading

0 comments on commit b96f54d

Please sign in to comment.