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:
parent
3447f7c703
commit
8bff4f1ea9
50
external/quickjs/quickjs-eval.js
vendored
50
external/quickjs/quickjs-eval.js
vendored
File diff suppressed because one or more lines are too long
28
gulpfile.js
28
gulpfile.js
@ -374,7 +374,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) {
|
||||
@ -1203,6 +1224,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")
|
||||
),
|
||||
@ -1791,7 +1815,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",
|
||||
|
175
src/pdf.sandbox.external.js
Normal file
175
src/pdf.sandbox.external.js
Normal file
@ -0,0 +1,175 @@
|
||||
/* 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.
|
||||
|
||||
class SandboxSupportBase {
|
||||
/**
|
||||
* @param {DOMWindow} - win
|
||||
*/
|
||||
constructor(win) {
|
||||
this.win = win;
|
||||
this.timeoutIds = new Map();
|
||||
|
||||
// Will be assigned after the sandbox is initialized
|
||||
this.commFun = null;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.commFunc = null;
|
||||
this.timeoutIds.forEach(([_, id]) => this.win.clearTimeout(id));
|
||||
this.timeoutIds = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} val - Export a value in the sandbox.
|
||||
*/
|
||||
exportValueToSandbox(val) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} val - Import a value from the sandbox.
|
||||
*/
|
||||
importValueFromSandbox(val) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} errorMessage - Create an error in the sandbox.
|
||||
*/
|
||||
createErrorForSandbox(errorMessage) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} name - Name of the function to call in the sandbox
|
||||
* @param {Array<Object>} args - Arguments of the function.
|
||||
*/
|
||||
callSandboxFunction(name, args) {
|
||||
try {
|
||||
args = this.exportValueToSandbox(args);
|
||||
this.commFun(name, args);
|
||||
} catch (e) {
|
||||
this.win.console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
createSandboxExternals() {
|
||||
// All the functions in externals object are called
|
||||
// from the sandbox.
|
||||
const externals = {
|
||||
setTimeout: (callbackId, nMilliseconds) => {
|
||||
if (
|
||||
typeof callbackId !== "number" ||
|
||||
typeof nMilliseconds !== "number"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const id = this.win.setTimeout(() => {
|
||||
this.timeoutIds.delete(callbackId);
|
||||
this.callSandboxFunction("timeoutCb", {
|
||||
callbackId,
|
||||
interval: false,
|
||||
});
|
||||
}, nMilliseconds);
|
||||
this.timeoutIds.set(callbackId, id);
|
||||
},
|
||||
clearTimeout: id => {
|
||||
this.win.clearTimeout(this.timeoutIds.get(id));
|
||||
this.timeoutIds.delete(id);
|
||||
},
|
||||
setInterval: (callbackId, nMilliseconds) => {
|
||||
if (
|
||||
typeof callbackId !== "number" ||
|
||||
typeof nMilliseconds !== "number"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const id = this.win.setInterval(() => {
|
||||
this.callSandboxFunction("timeoutCb", {
|
||||
callbackId,
|
||||
interval: true,
|
||||
});
|
||||
}, nMilliseconds);
|
||||
this.timeoutIds.set(callbackId, id);
|
||||
},
|
||||
clearInterval: id => {
|
||||
this.win.clearInterval(this.timeoutIds.get(id));
|
||||
this.timeoutIds.delete(id);
|
||||
},
|
||||
alert: cMsg => {
|
||||
if (typeof cMsg !== "string") {
|
||||
return;
|
||||
}
|
||||
this.win.alert(cMsg);
|
||||
},
|
||||
prompt: (cQuestion, cDefault) => {
|
||||
if (typeof cQuestion !== "string" || typeof cDefault !== "string") {
|
||||
return null;
|
||||
}
|
||||
return this.win.prompt(cQuestion, cDefault);
|
||||
},
|
||||
parseURL: cUrl => {
|
||||
const url = new this.win.URL(cUrl);
|
||||
const props = [
|
||||
"hash",
|
||||
"host",
|
||||
"hostname",
|
||||
"href",
|
||||
"origin",
|
||||
"password",
|
||||
"pathname",
|
||||
"port",
|
||||
"protocol",
|
||||
"search",
|
||||
"searchParams",
|
||||
"username",
|
||||
];
|
||||
|
||||
return Object.fromEntries(
|
||||
props.map(name => [name, url[name].toString()])
|
||||
);
|
||||
},
|
||||
send: data => {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
const event = new this.win.CustomEvent("updateFromSandbox", {
|
||||
detail: this.importValueFromSandbox(data),
|
||||
});
|
||||
this.win.dispatchEvent(event);
|
||||
},
|
||||
};
|
||||
Object.setPrototypeOf(externals, null);
|
||||
|
||||
return (name, args) => {
|
||||
try {
|
||||
const result = externals[name](...args);
|
||||
return this.exportValueToSandbox(result);
|
||||
} catch (error) {
|
||||
throw this.createErrorForSandbox(error?.toString() ?? "");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
|
||||
exports.SandboxSupportBase = SandboxSupportBase;
|
||||
} else {
|
||||
/* eslint-disable-next-line no-unused-vars, no-var */
|
||||
var EXPORTED_SYMBOLS = ["SandboxSupportBase"];
|
||||
}
|
@ -14,6 +14,7 @@
|
||||
*/
|
||||
|
||||
import ModuleLoader from "../external/quickjs/quickjs-eval.js";
|
||||
import { SandboxSupportBase } from "./pdf.sandbox.external.js";
|
||||
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const pdfjsVersion = PDFJSDev.eval("BUNDLE_VERSION");
|
||||
@ -23,79 +24,111 @@ const pdfjsBuild = PDFJSDev.eval("BUNDLE_BUILD");
|
||||
const TESTING =
|
||||
typeof PDFJSDev === "undefined" || PDFJSDev.test("!PRODUCTION || TESTING");
|
||||
|
||||
class SandboxSupport extends SandboxSupportBase {
|
||||
exportValueToSandbox(val) {
|
||||
// The communication with the Quickjs sandbox is based on strings
|
||||
// So we use JSON.stringfy to serialize
|
||||
return JSON.stringify(val);
|
||||
}
|
||||
|
||||
importValueFromSandbox(val) {
|
||||
return val;
|
||||
}
|
||||
|
||||
createErrorForSandbox(errorMessage) {
|
||||
return new Error(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
class Sandbox {
|
||||
constructor(module) {
|
||||
this._evalInSandbox = module.cwrap("evalInSandbox", null, [
|
||||
"string",
|
||||
"int",
|
||||
]);
|
||||
this._dispatchEventName = null;
|
||||
constructor(win, module) {
|
||||
this.support = new SandboxSupport(win, this);
|
||||
|
||||
// The "external" functions created in pdf.sandbox.external.js
|
||||
// are finally used here:
|
||||
// https://github.com/mozilla/pdf.js.quickjs/blob/main/src/myjs.js
|
||||
// They're called from the sandbox only.
|
||||
module.externalCall = this.support.createSandboxExternals();
|
||||
|
||||
this._module = module;
|
||||
this._alertOnError = 1;
|
||||
|
||||
// 0 to display error using console.error
|
||||
// else display error using window.alert
|
||||
this._alertOnError = 0;
|
||||
}
|
||||
|
||||
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 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} })`,
|
||||
];
|
||||
if (!TESTING) {
|
||||
code = code.concat(extra.map(name => `delete ${name};`));
|
||||
code.push("delete debugMe;");
|
||||
}
|
||||
this._evalInSandbox(code.join("\n"), this._alertOnError);
|
||||
this._dispatchEventName = data.dispatchEventName;
|
||||
}
|
||||
|
||||
dispatchEvent(event) {
|
||||
if (this._dispatchEventName === null) {
|
||||
throw new Error("Sandbox must have been initialized");
|
||||
}
|
||||
event = JSON.stringify(event);
|
||||
this._evalInSandbox(
|
||||
`app["${this._dispatchEventName}"](${event});`,
|
||||
this._alertOnError
|
||||
code.push("delete dump;");
|
||||
} else {
|
||||
code.unshift(
|
||||
`globalThis.sendResultForTesting = callExternalFunction.bind(null, "send");`
|
||||
);
|
||||
}
|
||||
|
||||
let success = false;
|
||||
try {
|
||||
success = !!this._module.ccall(
|
||||
"init",
|
||||
"number",
|
||||
["string", "number"],
|
||||
[code.join("\n"), this._alertOnError]
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
if (success) {
|
||||
this.support.commFun = this._module.cwrap("commFun", null, [
|
||||
"string",
|
||||
"string",
|
||||
]);
|
||||
} else {
|
||||
this.nukeSandbox();
|
||||
throw new Error("Cannot start sandbox");
|
||||
}
|
||||
}
|
||||
|
||||
dispatchEvent(event) {
|
||||
this.support.callSandboxFunction("dispatchEvent", event);
|
||||
}
|
||||
|
||||
dumpMemoryUse() {
|
||||
if (this._module) {
|
||||
this._module.ccall("dumpMemoryUse", null, []);
|
||||
}
|
||||
}
|
||||
|
||||
nukeSandbox() {
|
||||
this._dispatchEventName = null;
|
||||
if (this._module !== null) {
|
||||
this.support.destroy();
|
||||
this.support = null;
|
||||
this._module.ccall("nukeSandbox", null, []);
|
||||
this._module = null;
|
||||
this._evalInSandbox = null;
|
||||
}
|
||||
}
|
||||
|
||||
evalForTesting(code, key) {
|
||||
if (TESTING) {
|
||||
this._evalInSandbox(
|
||||
this._module.ccall(
|
||||
"evalInSandbox",
|
||||
null,
|
||||
["string", "int"],
|
||||
[
|
||||
`try {
|
||||
send({ id: "${key}", result: ${code} });
|
||||
sendResultForTesting([{ id: "${key}", result: ${code} }]);
|
||||
} catch (error) {
|
||||
send({ id: "${key}", result: error.message });
|
||||
sendResultForTesting([{ id: "${key}", result: error.message }]);
|
||||
}`,
|
||||
this._alertOnError
|
||||
this._alertOnError,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -103,7 +136,7 @@ class Sandbox {
|
||||
|
||||
function QuickJSSandbox() {
|
||||
return ModuleLoader().then(module => {
|
||||
return new Sandbox(module);
|
||||
return new Sandbox(window, module);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -49,27 +49,8 @@ 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 function is called thanks to the proxy
|
||||
// when we call app['random_string'] to dispatch the event.
|
||||
_dispatchEvent(pdfEvent) {
|
||||
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):
|
||||
@ -80,36 +61,76 @@ class App extends PDFObject {
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
this._timeoutIdsRegistry = new FinalizationRegistry(
|
||||
([timeoutId, isInterval]) => {
|
||||
if (isInterval) {
|
||||
this._clearInterval(timeoutId);
|
||||
} else {
|
||||
this._clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
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
|
||||
// when we call app['random_string'] to dispatch the event.
|
||||
_dispatchEvent(pdfEvent) {
|
||||
this._eventDispatcher.dispatch(pdfEvent);
|
||||
}
|
||||
|
||||
_registerTimeoutCallback(cExpr) {
|
||||
const id = this._timeoutCallbackId++;
|
||||
this._timeoutCallbackIds.set(id, cExpr);
|
||||
return id;
|
||||
}
|
||||
|
||||
_unregisterTimeoutCallback(id) {
|
||||
this._timeoutCallbackIds.delete(id);
|
||||
}
|
||||
|
||||
_evalCallback({ callbackId, interval }) {
|
||||
const expr = this._timeoutCallbackIds.get(callbackId);
|
||||
if (!interval) {
|
||||
this._unregisterTimeoutCallback(callbackId);
|
||||
}
|
||||
|
||||
if (expr) {
|
||||
this._globalEval(expr);
|
||||
}
|
||||
}
|
||||
this._timeoutIds.set(timeout, [id, interval]);
|
||||
|
||||
_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, interval]);
|
||||
this._timeoutIdsRegistry.register(timeout, id);
|
||||
}
|
||||
return timeout;
|
||||
}
|
||||
|
||||
_unregisterTimeout(timeout) {
|
||||
if (!this._timeoutIds || !this._timeoutIds.has(timeout)) {
|
||||
return;
|
||||
}
|
||||
const [id, interval] = this._timeoutIds.get(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() {
|
||||
|
@ -30,7 +30,6 @@ class InfoProxyHandler {
|
||||
class Doc extends PDFObject {
|
||||
constructor(data) {
|
||||
super(data);
|
||||
this.calculate = true;
|
||||
|
||||
this.baseURL = data.baseURL || "";
|
||||
this.calculate = true;
|
||||
|
@ -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;
|
||||
|
@ -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 };
|
||||
|
@ -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];
|
||||
|
@ -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) {
|
||||
|
@ -48,6 +48,10 @@ describe("Scripting", function () {
|
||||
send_queue.set(event.detail.id, event.detail);
|
||||
}
|
||||
};
|
||||
window.alert = value => {
|
||||
const command = "alert";
|
||||
send_queue.set(command, { command, value });
|
||||
};
|
||||
const promise = loadScript(sandboxBundleSrc).then(() => {
|
||||
return window.pdfjsSandbox.QuickJSSandbox();
|
||||
});
|
||||
@ -104,7 +108,6 @@ describe("Scripting", function () {
|
||||
},
|
||||
calculationOrder: [],
|
||||
appInfo: { language: "en-US", platform: "Linux x86_64" },
|
||||
dispatchEventName: "_dispatchMe",
|
||||
};
|
||||
sandbox.createSandbox(data);
|
||||
sandbox
|
||||
@ -132,7 +135,6 @@ describe("Scripting", function () {
|
||||
appInfo: { language: "en-US", platform: "Linux x86_64" },
|
||||
objects: {},
|
||||
calculationOrder: [],
|
||||
dispatchEventName: "_dispatchMe",
|
||||
});
|
||||
done();
|
||||
});
|
||||
@ -263,7 +265,6 @@ describe("Scripting", function () {
|
||||
},
|
||||
appInfo: { language: "en-US", platform: "Linux x86_64" },
|
||||
calculationOrder: [],
|
||||
dispatchEventName: "_dispatchMe",
|
||||
};
|
||||
sandbox.createSandbox(data);
|
||||
sandbox
|
||||
@ -301,7 +302,6 @@ describe("Scripting", function () {
|
||||
},
|
||||
appInfo: { language: "en-US", platform: "Linux x86_64" },
|
||||
calculationOrder: [],
|
||||
dispatchEventName: "_dispatchMe",
|
||||
};
|
||||
sandbox.createSandbox(data);
|
||||
sandbox
|
||||
@ -343,7 +343,6 @@ describe("Scripting", function () {
|
||||
},
|
||||
appInfo: { language: "en-US", platform: "Linux x86_64" },
|
||||
calculationOrder: [],
|
||||
dispatchEventName: "_dispatchMe",
|
||||
};
|
||||
sandbox.createSandbox(data);
|
||||
sandbox
|
||||
@ -384,7 +383,6 @@ describe("Scripting", function () {
|
||||
},
|
||||
appInfo: { language: "en-US", platform: "Linux x86_64" },
|
||||
calculationOrder: [],
|
||||
dispatchEventName: "_dispatchMe",
|
||||
};
|
||||
sandbox.createSandbox(data);
|
||||
sandbox
|
||||
@ -429,7 +427,6 @@ describe("Scripting", function () {
|
||||
},
|
||||
appInfo: { language: "en-US", platform: "Linux x86_64" },
|
||||
calculationOrder: [refId2],
|
||||
dispatchEventName: "_dispatchMe",
|
||||
};
|
||||
sandbox.createSandbox(data);
|
||||
sandbox
|
||||
@ -458,7 +455,6 @@ describe("Scripting", function () {
|
||||
appInfo: { language: "en-US", platform: "Linux x86_64" },
|
||||
objects: {},
|
||||
calculationOrder: [],
|
||||
dispatchEventName: "_dispatchMe",
|
||||
});
|
||||
done();
|
||||
});
|
||||
@ -553,7 +549,6 @@ describe("Scripting", function () {
|
||||
appInfo: { language: "en-US", platform: "Linux x86_64" },
|
||||
objects: {},
|
||||
calculationOrder: [],
|
||||
dispatchEventName: "_dispatchMe",
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
19
web/app.js
19
web/app.js
@ -19,7 +19,6 @@ import {
|
||||
AutoPrintRegExp,
|
||||
DEFAULT_SCALE_VALUE,
|
||||
EventBus,
|
||||
generateRandomStringForSandbox,
|
||||
getActiveOrFocusedElement,
|
||||
getPDFFileNameFromURL,
|
||||
isValidRotation,
|
||||
@ -1483,10 +1482,6 @@ const PDFViewerApplication = {
|
||||
const { id, command, value } = detail;
|
||||
if (!id) {
|
||||
switch (command) {
|
||||
case "alert":
|
||||
// eslint-disable-next-line no-alert
|
||||
window.alert(value);
|
||||
break;
|
||||
case "clear":
|
||||
console.clear();
|
||||
break;
|
||||
@ -1501,7 +1496,7 @@ const PDFViewerApplication = {
|
||||
return;
|
||||
case "print":
|
||||
this.triggerPrinting();
|
||||
return;
|
||||
break;
|
||||
case "println":
|
||||
console.log(value);
|
||||
break;
|
||||
@ -1511,7 +1506,7 @@ const PDFViewerApplication = {
|
||||
} else {
|
||||
this.pdfViewer.currentScale = value;
|
||||
}
|
||||
return;
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -1551,8 +1546,6 @@ const PDFViewerApplication = {
|
||||
window.addEventListener("mouseup", mouseUp);
|
||||
this._scriptingInstance.events.set("mouseup", mouseUp);
|
||||
|
||||
const dispatchEventName = generateRandomStringForSandbox(objects);
|
||||
|
||||
if (!this._contentLength) {
|
||||
// Always waiting for the entire PDF document to be loaded will, most
|
||||
// likely, delay sandbox-creation too much in the general case for all
|
||||
@ -1569,9 +1562,9 @@ const PDFViewerApplication = {
|
||||
const filename =
|
||||
this._contentDispositionFilename || getPDFFileNameFromURL(this.url);
|
||||
|
||||
scripting.createSandbox({
|
||||
try {
|
||||
await scripting.createSandbox({
|
||||
objects,
|
||||
dispatchEventName,
|
||||
calculationOrder,
|
||||
appInfo: {
|
||||
platform: navigator.platform,
|
||||
@ -1587,6 +1580,10 @@ const PDFViewerApplication = {
|
||||
URL: this.url,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
this._destroyScriptingInstance();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -256,15 +256,21 @@ class FirefoxComDataRangeTransport extends PDFDataRangeTransport {
|
||||
|
||||
class FirefoxScripting {
|
||||
static createSandbox(data) {
|
||||
FirefoxCom.requestSync("createSandbox", data);
|
||||
return new Promise(resolve => {
|
||||
FirefoxCom.request("createSandbox", data, resolve);
|
||||
}).then(success => {
|
||||
if (!success) {
|
||||
throw new Error("Cannot start sandbox");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static dispatchEventInSandbox(event) {
|
||||
FirefoxCom.requestSync("dispatchEventInSandbox", event);
|
||||
static async dispatchEventInSandbox(event) {
|
||||
FirefoxCom.request("dispatchEventInSandbox", event);
|
||||
}
|
||||
|
||||
static destroySandbox() {
|
||||
FirefoxCom.requestSync("destroySandbox", null);
|
||||
static async destroySandbox() {
|
||||
FirefoxCom.request("destroySandbox", null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1015,42 +1015,6 @@ function getActiveOrFocusedElement() {
|
||||
return curActiveOrFocused;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random string which is not define somewhere in actions.
|
||||
*
|
||||
* @param {Object} objects - The value returned by `getFieldObjects` in the API.
|
||||
* @returns {string} A unique string.
|
||||
*/
|
||||
function generateRandomStringForSandbox(objects) {
|
||||
const allObjects = Object.values(objects).flat(2);
|
||||
const actions = allObjects
|
||||
.filter(obj => !!obj.actions)
|
||||
.map(obj => Object.values(obj.actions))
|
||||
.flat(2);
|
||||
|
||||
while (true) {
|
||||
const name = new Uint8Array(64);
|
||||
if (typeof crypto !== "undefined") {
|
||||
crypto.getRandomValues(name);
|
||||
} else {
|
||||
for (let i = 0, ii = name.length; i < ii; i++) {
|
||||
name[i] = Math.floor(256 * Math.random());
|
||||
}
|
||||
}
|
||||
|
||||
const nameString =
|
||||
"_" +
|
||||
btoa(
|
||||
Array.from(name)
|
||||
.map(x => String.fromCharCode(x))
|
||||
.join("")
|
||||
);
|
||||
if (actions.every(action => !action.includes(nameString))) {
|
||||
return nameString;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
AutoPrintRegExp,
|
||||
CSS_UNITS,
|
||||
@ -1074,7 +1038,6 @@ export {
|
||||
NullL10n,
|
||||
EventBus,
|
||||
ProgressBar,
|
||||
generateRandomStringForSandbox,
|
||||
getPDFFileNameFromURL,
|
||||
noContextMenuHandler,
|
||||
parseQueryString,
|
||||
|
Loading…
Reference in New Issue
Block a user