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

File diff suppressed because one or more lines are too long

View File

@ -374,7 +374,28 @@ function createScriptingBundle(defines, extraOptions = undefined) {
.src("./src/pdf.scripting.js") .src("./src/pdf.scripting.js")
.pipe(webpack2Stream(scriptingFileConfig)) .pipe(webpack2Stream(scriptingFileConfig))
.pipe(replaceWebpackRequire()) .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) { function createTemporaryScriptingBundle(defines, extraOptions = undefined) {
@ -1203,6 +1224,9 @@ gulp.task(
createScriptingBundle(defines).pipe( createScriptingBundle(defines).pipe(
gulp.dest(MOZCENTRAL_CONTENT_DIR + "build") gulp.dest(MOZCENTRAL_CONTENT_DIR + "build")
), ),
createSandboxExternal(defines).pipe(
gulp.dest(MOZCENTRAL_CONTENT_DIR + "build")
),
createWorkerBundle(defines).pipe( createWorkerBundle(defines).pipe(
gulp.dest(MOZCENTRAL_CONTENT_DIR + "build") gulp.dest(MOZCENTRAL_CONTENT_DIR + "build")
), ),
@ -1791,7 +1815,7 @@ gulp.task(
gulp.task("watch-dev-sandbox", function () { gulp.task("watch-dev-sandbox", function () {
gulp.watch( gulp.watch(
[ [
"src/pdf.{sandbox,scripting}.js", "src/pdf.{sandbox,sandbox.external,scripting}.js",
"src/scripting_api/*.js", "src/scripting_api/*.js",
"src/shared/scripting_utils.js", "src/shared/scripting_utils.js",
"external/quickjs/*.js", "external/quickjs/*.js",

175
src/pdf.sandbox.external.js Normal file
View 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"];
}

View File

@ -14,6 +14,7 @@
*/ */
import ModuleLoader from "../external/quickjs/quickjs-eval.js"; import ModuleLoader from "../external/quickjs/quickjs-eval.js";
import { SandboxSupportBase } from "./pdf.sandbox.external.js";
/* eslint-disable-next-line no-unused-vars */ /* eslint-disable-next-line no-unused-vars */
const pdfjsVersion = PDFJSDev.eval("BUNDLE_VERSION"); const pdfjsVersion = PDFJSDev.eval("BUNDLE_VERSION");
@ -23,79 +24,111 @@ const pdfjsBuild = PDFJSDev.eval("BUNDLE_BUILD");
const TESTING = const TESTING =
typeof PDFJSDev === "undefined" || PDFJSDev.test("!PRODUCTION || 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 { class Sandbox {
constructor(module) { constructor(win, module) {
this._evalInSandbox = module.cwrap("evalInSandbox", null, [ this.support = new SandboxSupport(win, this);
"string",
"int", // The "external" functions created in pdf.sandbox.external.js
]); // are finally used here:
this._dispatchEventName = null; // 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._module = module;
this._alertOnError = 1;
// 0 to display error using console.error
// else display error using window.alert
this._alertOnError = 0;
} }
create(data) { create(data) {
const sandboxData = JSON.stringify(data); const sandboxData = JSON.stringify(data);
const extra = [ const code = [
"send",
"setTimeout",
"clearTimeout",
"setInterval",
"clearInterval",
"crackURL",
];
const extraStr = extra.join(",");
let code = [
"exports = Object.create(null);",
"module = Object.create(null);",
// Next line is replaced by code from initialization.js // Next line is replaced by code from initialization.js
// when we create the bundle for the sandbox. // when we create the bundle for the sandbox.
PDFJSDev.eval("PDF_SCRIPTING_JS_SOURCE"), PDFJSDev.eval("PDF_SCRIPTING_JS_SOURCE"),
`data = ${sandboxData};`, `pdfjsScripting.initSandbox({ data: ${sandboxData} })`,
`module.exports.initSandbox({ data, extra: {${extraStr}}, out: this});`,
"delete exports;",
"delete module;",
"delete data;",
]; ];
if (!TESTING) { if (!TESTING) {
code = code.concat(extra.map(name => `delete ${name};`)); code.push("delete dump;");
code.push("delete debugMe;"); } else {
} code.unshift(
this._evalInSandbox(code.join("\n"), this._alertOnError); `globalThis.sendResultForTesting = callExternalFunction.bind(null, "send");`
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
); );
} }
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() { dumpMemoryUse() {
if (this._module) {
this._module.ccall("dumpMemoryUse", null, []); this._module.ccall("dumpMemoryUse", null, []);
} }
}
nukeSandbox() { nukeSandbox() {
this._dispatchEventName = null; if (this._module !== null) {
this.support.destroy();
this.support = null;
this._module.ccall("nukeSandbox", null, []); this._module.ccall("nukeSandbox", null, []);
this._module = null; this._module = null;
this._evalInSandbox = null; }
} }
evalForTesting(code, key) { evalForTesting(code, key) {
if (TESTING) { if (TESTING) {
this._evalInSandbox( this._module.ccall(
"evalInSandbox",
null,
["string", "int"],
[
`try { `try {
send({ id: "${key}", result: ${code} }); sendResultForTesting([{ id: "${key}", result: ${code} }]);
} catch (error) { } 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() { function QuickJSSandbox() {
return ModuleLoader().then(module => { return ModuleLoader().then(module => {
return new Sandbox(module); return new Sandbox(window, module);
}); });
} }

View File

@ -49,27 +49,8 @@ class App extends PDFObject {
data.calculationOrder, data.calculationOrder,
this._objects 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(); this._timeoutIds = new WeakMap();
// FinalizationRegistry isn't implemented in QuickJS
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
if (typeof FinalizationRegistry !== "undefined") { if (typeof FinalizationRegistry !== "undefined") {
// About setTimeOut/setInterval return values (specs): // About setTimeOut/setInterval return values (specs):
@ -80,36 +61,76 @@ class App extends PDFObject {
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
this._timeoutIdsRegistry = new FinalizationRegistry( this._timeoutIdsRegistry = new FinalizationRegistry(
([timeoutId, isInterval]) => { this._cleanTimeout.bind(this)
if (isInterval) {
this._clearInterval(timeoutId);
} else {
this._clearTimeout(timeoutId);
}
}
); );
} 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) { if (this._timeoutIdsRegistry) {
this._timeoutIdsRegistry.register(timeout, [id, interval]); this._timeoutIdsRegistry.register(timeout, id);
} }
return timeout;
} }
_unregisterTimeout(timeout) { _unregisterTimeout(timeout) {
if (!this._timeoutIds || !this._timeoutIds.has(timeout)) {
return;
}
const [id, interval] = this._timeoutIds.get(timeout);
if (this._timeoutIdsRegistry) { if (this._timeoutIdsRegistry) {
this._timeoutIdsRegistry.unregister(timeout); this._timeoutIdsRegistry.unregister(timeout);
} }
const data = this._timeoutIds.get(timeout);
if (!data) {
return;
}
this._timeoutIds.delete(timeout); this._timeoutIds.delete(timeout);
this._cleanTimeout(data);
}
_cleanTimeout({ callbackId, interval }) {
this._unregisterTimeoutCallback(callbackId);
if (interval) { if (interval) {
this._clearInterval(id); this._externalCall("clearInterval", [callbackId]);
} else { } else {
this._clearTimeout(id); this._externalCall("clearTimeout", [callbackId]);
} }
} }
@ -409,7 +430,7 @@ class App extends PDFObject {
oDoc = null, oDoc = null,
oCheckbox = null oCheckbox = null
) { ) {
this._send({ command: "alert", value: cMsg }); this._externalCall("alert", [cMsg]);
} }
beep() { beep() {
@ -425,11 +446,11 @@ class App extends PDFObject {
} }
clearInterval(oInterval) { clearInterval(oInterval) {
this.unregisterTimeout(oInterval); this._unregisterTimeout(oInterval);
} }
clearTimeOut(oTime) { clearTimeOut(oTime) {
this.unregisterTimeout(oTime); this._unregisterTimeout(oTime);
} }
endPriv() { endPriv() {
@ -524,8 +545,8 @@ class App extends PDFObject {
/* Not implemented */ /* Not implemented */
} }
response() { response(cQuestion, cTitle = "", cDefault = "", bPassword = "", cLabel = "") {
/* TODO or not */ return this._externalCall("prompt", [cQuestion, cDefault || ""]);
} }
setInterval(cExpr, nMilliseconds) { setInterval(cExpr, nMilliseconds) {
@ -537,11 +558,9 @@ class App extends PDFObject {
"Second argument of app.setInterval must be a number" "Second argument of app.setInterval must be a number"
); );
} }
const callbackId = this._registerTimeoutCallback(cExpr);
const id = this._setInterval(cExpr, nMilliseconds); this._externalCall("setInterval", [callbackId, nMilliseconds]);
const timeout = Object.create(null); return this._registerTimeout(callbackId, true);
this._registerTimeout(timeout, id, true);
return timeout;
} }
setTimeOut(cExpr, nMilliseconds) { setTimeOut(cExpr, nMilliseconds) {
@ -551,11 +570,9 @@ class App extends PDFObject {
if (typeof nMilliseconds !== "number") { if (typeof nMilliseconds !== "number") {
throw new TypeError("Second argument of app.setTimeOut must be a number"); throw new TypeError("Second argument of app.setTimeOut must be a number");
} }
const callbackId = this._registerTimeoutCallback(cExpr);
const id = this._setTimeout(cExpr, nMilliseconds); this._externalCall("setTimeout", [callbackId, nMilliseconds]);
const timeout = Object.create(null); return this._registerTimeout(callbackId, false);
this._registerTimeout(timeout, id, false);
return timeout;
} }
trustedFunction() { trustedFunction() {

View File

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

View File

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

View File

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

View File

@ -14,18 +14,7 @@
*/ */
class ProxyHandler { class ProxyHandler {
constructor(dispatchEventName) {
this.dispatchEventName = dispatchEventName;
}
get(obj, prop) { 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 // script may add some properties to the object
if (prop in obj._expandos) { if (prop in obj._expandos) {
const val = obj._expandos[prop]; const val = obj._expandos[prop];

View File

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

View File

@ -48,6 +48,10 @@ describe("Scripting", function () {
send_queue.set(event.detail.id, event.detail); 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(() => { const promise = loadScript(sandboxBundleSrc).then(() => {
return window.pdfjsSandbox.QuickJSSandbox(); return window.pdfjsSandbox.QuickJSSandbox();
}); });
@ -104,7 +108,6 @@ describe("Scripting", function () {
}, },
calculationOrder: [], calculationOrder: [],
appInfo: { language: "en-US", platform: "Linux x86_64" }, appInfo: { language: "en-US", platform: "Linux x86_64" },
dispatchEventName: "_dispatchMe",
}; };
sandbox.createSandbox(data); sandbox.createSandbox(data);
sandbox sandbox
@ -132,7 +135,6 @@ describe("Scripting", function () {
appInfo: { language: "en-US", platform: "Linux x86_64" }, appInfo: { language: "en-US", platform: "Linux x86_64" },
objects: {}, objects: {},
calculationOrder: [], calculationOrder: [],
dispatchEventName: "_dispatchMe",
}); });
done(); done();
}); });
@ -263,7 +265,6 @@ describe("Scripting", function () {
}, },
appInfo: { language: "en-US", platform: "Linux x86_64" }, appInfo: { language: "en-US", platform: "Linux x86_64" },
calculationOrder: [], calculationOrder: [],
dispatchEventName: "_dispatchMe",
}; };
sandbox.createSandbox(data); sandbox.createSandbox(data);
sandbox sandbox
@ -301,7 +302,6 @@ describe("Scripting", function () {
}, },
appInfo: { language: "en-US", platform: "Linux x86_64" }, appInfo: { language: "en-US", platform: "Linux x86_64" },
calculationOrder: [], calculationOrder: [],
dispatchEventName: "_dispatchMe",
}; };
sandbox.createSandbox(data); sandbox.createSandbox(data);
sandbox sandbox
@ -343,7 +343,6 @@ describe("Scripting", function () {
}, },
appInfo: { language: "en-US", platform: "Linux x86_64" }, appInfo: { language: "en-US", platform: "Linux x86_64" },
calculationOrder: [], calculationOrder: [],
dispatchEventName: "_dispatchMe",
}; };
sandbox.createSandbox(data); sandbox.createSandbox(data);
sandbox sandbox
@ -384,7 +383,6 @@ describe("Scripting", function () {
}, },
appInfo: { language: "en-US", platform: "Linux x86_64" }, appInfo: { language: "en-US", platform: "Linux x86_64" },
calculationOrder: [], calculationOrder: [],
dispatchEventName: "_dispatchMe",
}; };
sandbox.createSandbox(data); sandbox.createSandbox(data);
sandbox sandbox
@ -429,7 +427,6 @@ describe("Scripting", function () {
}, },
appInfo: { language: "en-US", platform: "Linux x86_64" }, appInfo: { language: "en-US", platform: "Linux x86_64" },
calculationOrder: [refId2], calculationOrder: [refId2],
dispatchEventName: "_dispatchMe",
}; };
sandbox.createSandbox(data); sandbox.createSandbox(data);
sandbox sandbox
@ -458,7 +455,6 @@ describe("Scripting", function () {
appInfo: { language: "en-US", platform: "Linux x86_64" }, appInfo: { language: "en-US", platform: "Linux x86_64" },
objects: {}, objects: {},
calculationOrder: [], calculationOrder: [],
dispatchEventName: "_dispatchMe",
}); });
done(); done();
}); });
@ -553,7 +549,6 @@ describe("Scripting", function () {
appInfo: { language: "en-US", platform: "Linux x86_64" }, appInfo: { language: "en-US", platform: "Linux x86_64" },
objects: {}, objects: {},
calculationOrder: [], calculationOrder: [],
dispatchEventName: "_dispatchMe",
}); });
done(); done();
}); });

View File

@ -19,7 +19,6 @@ import {
AutoPrintRegExp, AutoPrintRegExp,
DEFAULT_SCALE_VALUE, DEFAULT_SCALE_VALUE,
EventBus, EventBus,
generateRandomStringForSandbox,
getActiveOrFocusedElement, getActiveOrFocusedElement,
getPDFFileNameFromURL, getPDFFileNameFromURL,
isValidRotation, isValidRotation,
@ -1483,10 +1482,6 @@ const PDFViewerApplication = {
const { id, command, value } = detail; const { id, command, value } = detail;
if (!id) { if (!id) {
switch (command) { switch (command) {
case "alert":
// eslint-disable-next-line no-alert
window.alert(value);
break;
case "clear": case "clear":
console.clear(); console.clear();
break; break;
@ -1501,7 +1496,7 @@ const PDFViewerApplication = {
return; return;
case "print": case "print":
this.triggerPrinting(); this.triggerPrinting();
return; break;
case "println": case "println":
console.log(value); console.log(value);
break; break;
@ -1511,7 +1506,7 @@ const PDFViewerApplication = {
} else { } else {
this.pdfViewer.currentScale = value; this.pdfViewer.currentScale = value;
} }
return; break;
} }
return; return;
} }
@ -1551,8 +1546,6 @@ const PDFViewerApplication = {
window.addEventListener("mouseup", mouseUp); window.addEventListener("mouseup", mouseUp);
this._scriptingInstance.events.set("mouseup", mouseUp); this._scriptingInstance.events.set("mouseup", mouseUp);
const dispatchEventName = generateRandomStringForSandbox(objects);
if (!this._contentLength) { if (!this._contentLength) {
// Always waiting for the entire PDF document to be loaded will, most // Always waiting for the entire PDF document to be loaded will, most
// likely, delay sandbox-creation too much in the general case for all // likely, delay sandbox-creation too much in the general case for all
@ -1569,9 +1562,9 @@ const PDFViewerApplication = {
const filename = const filename =
this._contentDispositionFilename || getPDFFileNameFromURL(this.url); this._contentDispositionFilename || getPDFFileNameFromURL(this.url);
scripting.createSandbox({ try {
await scripting.createSandbox({
objects, objects,
dispatchEventName,
calculationOrder, calculationOrder,
appInfo: { appInfo: {
platform: navigator.platform, platform: navigator.platform,
@ -1587,6 +1580,10 @@ const PDFViewerApplication = {
URL: this.url, URL: this.url,
}, },
}); });
} catch (error) {
console.error(error);
this._destroyScriptingInstance();
}
}, },
/** /**

View File

@ -256,15 +256,21 @@ class FirefoxComDataRangeTransport extends PDFDataRangeTransport {
class FirefoxScripting { class FirefoxScripting {
static createSandbox(data) { 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) { static async dispatchEventInSandbox(event) {
FirefoxCom.requestSync("dispatchEventInSandbox", event); FirefoxCom.request("dispatchEventInSandbox", event);
} }
static destroySandbox() { static async destroySandbox() {
FirefoxCom.requestSync("destroySandbox", null); FirefoxCom.request("destroySandbox", null);
} }
} }

View File

@ -1015,42 +1015,6 @@ function getActiveOrFocusedElement() {
return curActiveOrFocused; 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 { export {
AutoPrintRegExp, AutoPrintRegExp,
CSS_UNITS, CSS_UNITS,
@ -1074,7 +1038,6 @@ export {
NullL10n, NullL10n,
EventBus, EventBus,
ProgressBar, ProgressBar,
generateRandomStringForSandbox,
getPDFFileNameFromURL, getPDFFileNameFromURL,
noContextMenuHandler, noContextMenuHandler,
parseQueryString, parseQueryString,