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 8, 2020
1 parent d784af3 commit 976f0d5
Show file tree
Hide file tree
Showing 17 changed files with 410 additions and 204 deletions.
50 changes: 24 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 @@ -362,7 +362,19 @@ 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 copyScriptingExtra() {
return gulp
.src("./src/scripting_api/extra.js")
.pipe(rename("pdf.scripting.extra.js"))
.pipe(replace("export { buildExtra };", ""));
}

function createTemporaryScriptingBundle(defines, extraOptions = undefined) {
Expand Down Expand Up @@ -1187,6 +1199,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
77 changes: 45 additions & 32 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 { buildExtra } from "./scripting_api/extra.js";
import ModuleLoader from "../external/quickjs/quickjs-eval.js";

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

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

create(data) {
const sandboxData = JSON.stringify(data);
const extra = [
"send",
"setTimeout",
"clearTimeout",
"setInterval",
"clearInterval",
"crackURL",
];
const extra = ["send", "globalEval"].concat(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.
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}, extra: {${extraStr}}, out: this})`,
];
let cleanup = ["delete pdfjsScripting;"];
if (!TESTING) {
code = code.concat(extra.map(name => `delete ${name};`));
code.push("delete debugMe;");
cleanup = cleanup.concat(extra.map(name => `delete ${name};`));
cleanup.push("delete debugMe;");
}
this._evalInSandbox(code.join("\n"), this._alertOnError);
this._dispatchEventName = data.dispatchEventName;
const success = !!this._module.ccall(
"initSandbox",
"int",
["string", "string", "int"],
[code.join("\n"), cleanup.join("\n"), this._alertOnError]
);
if (!success) {
this.nukeSandbox();
}
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 @@ -102,8 +101,22 @@ class Sandbox {
}

function QuickJSSandbox() {
const handlers = {
eval(s) {},
clone(obj) {
return window.JSON.stringify(obj);
},
newTypeError(err) {
return err.message;
},
};
window.sandboxExtra = buildExtra(window, handlers);
const extraNames = Object.getOwnPropertyNames(window.sandboxExtra);

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

Expand Down
4 changes: 1 addition & 3 deletions src/scripting_api/aform.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
*/

class AForm {
constructor(document, app, util) {
this._document = document;
constructor(app, util) {
this._app = app;
this._util = util;
}
Expand All @@ -28,7 +27,6 @@ class AForm {
strCurrency,
bCurrencyPrepend
) {
const event = this._document._event;
if (!event.value) {
return;
}
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
Loading

0 comments on commit 976f0d5

Please sign in to comment.