In order to simplify m-c code, move some in pdf.js

* 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
This commit is contained in:
Calixte Denizet 2020-12-04 00:39:50 +01:00
parent 3447f7c703
commit 8bff4f1ea9
14 changed files with 472 additions and 265 deletions

View file

@ -49,15 +49,28 @@ 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._timeoutIds = new WeakMap();
// eslint-disable-next-line no-undef
if (typeof FinalizationRegistry !== "undefined") {
// About setTimeOut/setInterval return values (specs):
// The return value of this method must be held in a
// JavaScript variable.
// Otherwise, the timeout object is subject to garbage-collection,
// which would cause the clock to stop.
// eslint-disable-next-line no-undef
this._timeoutIdsRegistry = new FinalizationRegistry(
this._cleanTimeout.bind(this)
);
} else {
this._timeoutIdsRegistry = null;
}
this._timeoutCallbackIds = new Map();
this._timeoutCallbackId = 0;
this._globalEval = data.globalEval;
this._externalCall = data.externalCall;
}
// This function is called thanks to the proxy
@ -66,50 +79,58 @@ class App extends PDFObject {
this._eventDispatcher.dispatch(pdfEvent);
}
_registerTimeout(timeout, id, interval) {
if (!this._timeoutIds) {
this._timeoutIds = new WeakMap();
// FinalizationRegistry isn't implemented in QuickJS
// eslint-disable-next-line no-undef
if (typeof FinalizationRegistry !== "undefined") {
// About setTimeOut/setInterval return values (specs):
// The return value of this method must be held in a
// JavaScript variable.
// Otherwise, the timeout object is subject to garbage-collection,
// which would cause the clock to stop.
_registerTimeoutCallback(cExpr) {
const id = this._timeoutCallbackId++;
this._timeoutCallbackIds.set(id, cExpr);
return id;
}
// eslint-disable-next-line no-undef
this._timeoutIdsRegistry = new FinalizationRegistry(
([timeoutId, isInterval]) => {
if (isInterval) {
this._clearInterval(timeoutId);
} else {
this._clearTimeout(timeoutId);
}
}
);
}
_unregisterTimeoutCallback(id) {
this._timeoutCallbackIds.delete(id);
}
_evalCallback({ callbackId, interval }) {
const expr = this._timeoutCallbackIds.get(callbackId);
if (!interval) {
this._unregisterTimeoutCallback(callbackId);
}
this._timeoutIds.set(timeout, [id, interval]);
if (this._timeoutIdsRegistry) {
this._timeoutIdsRegistry.register(timeout, [id, interval]);
if (expr) {
this._globalEval(expr);
}
}
_unregisterTimeout(timeout) {
if (!this._timeoutIds || !this._timeoutIds.has(timeout)) {
return;
_registerTimeout(callbackId, interval) {
const timeout = Object.create(null);
const id = { callbackId, interval };
this._timeoutIds.set(timeout, id);
if (this._timeoutIdsRegistry) {
this._timeoutIdsRegistry.register(timeout, id);
}
const [id, interval] = this._timeoutIds.get(timeout);
return timeout;
}
_unregisterTimeout(timeout) {
if (this._timeoutIdsRegistry) {
this._timeoutIdsRegistry.unregister(timeout);
}
const data = this._timeoutIds.get(timeout);
if (!data) {
return;
}
this._timeoutIds.delete(timeout);
this._cleanTimeout(data);
}
_cleanTimeout({ callbackId, interval }) {
this._unregisterTimeoutCallback(callbackId);
if (interval) {
this._clearInterval(id);
this._externalCall("clearInterval", [callbackId]);
} else {
this._clearTimeout(id);
this._externalCall("clearTimeout", [callbackId]);
}
}
@ -409,7 +430,7 @@ class App extends PDFObject {
oDoc = null,
oCheckbox = null
) {
this._send({ command: "alert", value: cMsg });
this._externalCall("alert", [cMsg]);
}
beep() {
@ -425,11 +446,11 @@ class App extends PDFObject {
}
clearInterval(oInterval) {
this.unregisterTimeout(oInterval);
this._unregisterTimeout(oInterval);
}
clearTimeOut(oTime) {
this.unregisterTimeout(oTime);
this._unregisterTimeout(oTime);
}
endPriv() {
@ -524,8 +545,8 @@ class App extends PDFObject {
/* Not implemented */
}
response() {
/* TODO or not */
response(cQuestion, cTitle = "", cDefault = "", bPassword = "", cLabel = "") {
return this._externalCall("prompt", [cQuestion, cDefault || ""]);
}
setInterval(cExpr, nMilliseconds) {
@ -537,11 +558,9 @@ class App extends PDFObject {
"Second argument of app.setInterval must be a number"
);
}
const id = this._setInterval(cExpr, nMilliseconds);
const timeout = Object.create(null);
this._registerTimeout(timeout, id, true);
return timeout;
const callbackId = this._registerTimeoutCallback(cExpr);
this._externalCall("setInterval", [callbackId, nMilliseconds]);
return this._registerTimeout(callbackId, true);
}
setTimeOut(cExpr, nMilliseconds) {
@ -551,11 +570,9 @@ 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 timeout = Object.create(null);
this._registerTimeout(timeout, id, false);
return timeout;
const callbackId = this._registerTimeoutCallback(cExpr);
this._externalCall("setTimeout", [callbackId, nMilliseconds]);
return this._registerTimeout(callbackId, false);
}
trustedFunction() {

View file

@ -30,7 +30,6 @@ class InfoProxyHandler {
class Doc extends PDFObject {
constructor(data) {
super(data);
this.calculate = true;
this.baseURL = data.baseURL || "";
this.calculate = true;

View file

@ -180,11 +180,7 @@ class Field extends PDFObject {
}
} catch (error) {
event.rc = false;
const value =
`"${error.toString()}" for event ` +
`"${eventName}" in object ${this._id}.` +
`\n${error.stack}`;
this._send({ command: "error", value });
throw error;
}
return true;

View file

@ -35,16 +35,20 @@ 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) {
delete globalThis.pdfjsScripting;
// externalCall is a function to call a function defined
// outside the sandbox.
// (see src/pdf.sandbox.external.js).
const externalCall = globalThis.callExternalFunction;
delete globalThis.callExternalFunction;
// eslint-disable-next-line no-eval
const globalEval = code => globalThis.eval(code);
const send = data => externalCall("send", [data]);
const proxyHandler = new ProxyHandler();
const { data } = params;
const doc = new Doc({
send,
...data.docInfo,
@ -52,21 +56,21 @@ function initSandbox({ data, extra, out }) {
const _document = { obj: doc, wrapped: new Proxy(doc, proxyHandler) };
const app = new App({
send,
setTimeout,
clearTimeout,
setInterval,
clearInterval,
globalEval,
externalCall,
_document,
calculationOrder: data.calculationOrder,
proxyHandler,
...data.appInfo,
});
const util = new Util({ crackURL });
const util = new Util({ externalCall });
const aform = new AForm(doc, app, util);
for (const [name, objs] of Object.entries(data.objects)) {
const obj = objs[0];
obj.send = send;
obj.globalEval = globalEval;
obj.doc = _document.wrapped;
const field = new Field(obj);
const wrapped = new Proxy(field, proxyHandler);
@ -74,28 +78,44 @@ function initSandbox({ data, extra, out }) {
app._objects[obj.id] = { obj: field, wrapped };
}
out.global = Object.create(null);
out.app = new Proxy(app, proxyHandler);
out.color = new Proxy(new Color(), proxyHandler);
out.console = new Proxy(new Console({ send }), proxyHandler);
out.util = new Proxy(util, proxyHandler);
out.border = Border;
out.cursor = Cursor;
out.display = Display;
out.font = Font;
out.highlight = Highlight;
out.position = Position;
out.scaleHow = ScaleHow;
out.scaleWhen = ScaleWhen;
out.style = Style;
out.trans = Trans;
out.zoomtype = ZoomType;
globalThis.event = null;
globalThis.global = Object.create(null);
globalThis.app = new Proxy(app, proxyHandler);
globalThis.doc = _document.wrapped;
globalThis.color = new Proxy(new Color(), proxyHandler);
globalThis.console = new Proxy(new Console({ send }), proxyHandler);
globalThis.util = new Proxy(util, proxyHandler);
globalThis.border = Border;
globalThis.cursor = Cursor;
globalThis.display = Display;
globalThis.font = Font;
globalThis.highlight = Highlight;
globalThis.position = Position;
globalThis.scaleHow = ScaleHow;
globalThis.scaleWhen = ScaleWhen;
globalThis.style = Style;
globalThis.trans = Trans;
globalThis.zoomtype = ZoomType;
for (const name of Object.getOwnPropertyNames(AForm.prototype)) {
if (name !== "constructor" && !name.startsWith("_")) {
out[name] = aform[name].bind(aform);
globalThis[name] = aform[name].bind(aform);
}
}
const functions = {
dispatchEvent: app._dispatchEvent.bind(app),
timeoutCb: app._evalCallback.bind(app),
};
return (name, args) => {
try {
functions[name](args);
} catch (error) {
const value = `${error.toString()}\n${error.stack}`;
send({ command: "error", value });
}
};
}
export { initSandbox };

View file

@ -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];

View file

@ -19,7 +19,6 @@ class Util extends PDFObject {
constructor(data) {
super(data);
this._crackURL = data.crackURL;
this._scandCache = new Map();
this._months = [
"January",
@ -46,13 +45,9 @@ class Util extends PDFObject {
];
this.MILLISECONDS_IN_DAY = 86400000;
this.MILLISECONDS_IN_WEEK = 604800000;
}
crackURL(cURL) {
if (typeof cURL !== "string") {
throw new TypeError("First argument of util.crackURL must be a string");
}
return this._crackURL(cURL);
// used with crackURL
this._externalCall = data.externalCall;
}
printf(...args) {