Merge pull request #16007 from Snuffleupagus/getDocument-params-init

Re-factor the parameter parsing/validation in `getDocument`
This commit is contained in:
Tim van der Meij 2023-02-05 13:16:59 +01:00 committed by GitHub
commit 05d821e680
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 213 additions and 230 deletions

View File

@ -274,157 +274,96 @@ function getDocument(src) {
} }
const task = new PDFDocumentLoadingTask(); const task = new PDFDocumentLoadingTask();
const params = Object.create(null); const url = src.url ? getUrlProp(src.url) : null;
let rangeTransport = null, const data = src.data ? getDataProp(src.data) : null;
worker = null; const httpHeaders = src.httpHeaders || null;
const withCredentials = src.withCredentials === true;
for (const key in src) { const password = src.password ?? null;
const val = src[key]; const rangeTransport =
src.range instanceof PDFDataRangeTransport ? src.range : null;
switch (key) { const rangeChunkSize =
case "url": Number.isInteger(src.rangeChunkSize) && src.rangeChunkSize > 0
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) { ? src.rangeChunkSize
continue; // The 'url' is unused with `PDFDataRangeTransport`. : DEFAULT_RANGE_CHUNK_SIZE;
} else { let worker = src.worker instanceof PDFWorker ? src.worker : null;
if (val instanceof URL) { const verbosity = src.verbosity;
params[key] = val.href;
continue;
}
try {
// The full path is required in the 'url' field.
params[key] = new URL(val, window.location).href;
continue;
} catch (ex) {
if (
typeof PDFJSDev !== "undefined" &&
PDFJSDev.test("GENERIC") &&
isNodeJS &&
typeof val === "string"
) {
break; // Use the url as-is in Node.js environments.
}
}
throw new Error(
"Invalid PDF url data: " +
"either string or URL-object is expected in the url property."
);
}
case "range":
rangeTransport = val;
continue;
case "worker":
worker = val;
continue;
case "data":
// Converting string or array-like data to Uint8Array.
if (
typeof PDFJSDev !== "undefined" &&
PDFJSDev.test("GENERIC") &&
isNodeJS &&
typeof Buffer !== "undefined" && // eslint-disable-line no-undef
val instanceof Buffer // eslint-disable-line no-undef
) {
params[key] = new Uint8Array(val);
} else if (
val instanceof Uint8Array &&
val.byteLength === val.buffer.byteLength
) {
// Use the data as-is when it's already a Uint8Array that completely
// "utilizes" its underlying ArrayBuffer, to prevent any possible
// issues when transferring it to the worker-thread.
break;
} else if (typeof val === "string") {
params[key] = stringToBytes(val);
} else if (
(typeof val === "object" && val !== null && !isNaN(val.length)) ||
isArrayBuffer(val)
) {
params[key] = new Uint8Array(val);
} else {
throw new Error(
"Invalid PDF binary data: either TypedArray, " +
"string, or array-like object is expected in the data property."
);
}
continue;
}
params[key] = val;
}
params.cMapPacked = params.cMapPacked !== false;
params.CMapReaderFactory =
params.CMapReaderFactory || DefaultCMapReaderFactory;
params.StandardFontDataFactory =
params.StandardFontDataFactory || DefaultStandardFontDataFactory;
params.ignoreErrors = params.stopAtErrors !== true;
params.fontExtraProperties = params.fontExtraProperties === true;
params.pdfBug = params.pdfBug === true;
params.enableXfa = params.enableXfa === true;
if (!Number.isInteger(params.rangeChunkSize) || params.rangeChunkSize < 1) {
params.rangeChunkSize = DEFAULT_RANGE_CHUNK_SIZE;
}
if (
typeof params.docBaseUrl !== "string" ||
isDataScheme(params.docBaseUrl)
) {
// Ignore "data:"-URLs, since they can't be used to recover valid absolute // Ignore "data:"-URLs, since they can't be used to recover valid absolute
// URLs anyway. We want to avoid sending them to the worker-thread, since // URLs anyway. We want to avoid sending them to the worker-thread, since
// they contain the *entire* PDF document and can thus be arbitrarily long. // they contain the *entire* PDF document and can thus be arbitrarily long.
params.docBaseUrl = null; const docBaseUrl =
} typeof src.docBaseUrl === "string" && !isDataScheme(src.docBaseUrl)
if (!Number.isInteger(params.maxImageSize) || params.maxImageSize < -1) { ? src.docBaseUrl
params.maxImageSize = -1; : null;
} const cMapUrl = typeof src.cMapUrl === "string" ? src.cMapUrl : null;
if (typeof params.cMapUrl !== "string") { const cMapPacked = src.cMapPacked !== false;
params.cMapUrl = null; const CMapReaderFactory = src.CMapReaderFactory || DefaultCMapReaderFactory;
} const standardFontDataUrl =
if (typeof params.standardFontDataUrl !== "string") { typeof src.standardFontDataUrl === "string"
params.standardFontDataUrl = null; ? src.standardFontDataUrl
} : null;
if (typeof params.useWorkerFetch !== "boolean") { const StandardFontDataFactory =
params.useWorkerFetch = src.StandardFontDataFactory || DefaultStandardFontDataFactory;
(typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) || const ignoreErrors = src.stopAtErrors !== true;
(params.CMapReaderFactory === DOMCMapReaderFactory && const maxImageSize =
params.StandardFontDataFactory === DOMStandardFontDataFactory && Number.isInteger(src.maxImageSize) && src.maxImageSize > -1
isValidFetchUrl(params.cMapUrl, document.baseURI) && ? src.maxImageSize
isValidFetchUrl(params.standardFontDataUrl, document.baseURI)); : -1;
} const isEvalSupported = src.isEvalSupported !== false;
if (typeof params.isEvalSupported !== "boolean") { const isOffscreenCanvasSupported =
params.isEvalSupported = true; typeof src.isOffscreenCanvasSupported === "boolean"
} ? src.isOffscreenCanvasSupported
if (typeof params.isOffscreenCanvasSupported !== "boolean") { : !isNodeJS;
params.isOffscreenCanvasSupported = !isNodeJS; const disableFontFace =
} typeof src.disableFontFace === "boolean" ? src.disableFontFace : isNodeJS;
if (typeof params.disableFontFace !== "boolean") { const fontExtraProperties = src.fontExtraProperties === true;
params.disableFontFace = isNodeJS; const enableXfa = src.enableXfa === true;
} const ownerDocument = src.ownerDocument || globalThis.document;
if (typeof params.useSystemFonts !== "boolean") { const disableRange = src.disableRange === true;
params.useSystemFonts = !isNodeJS && !params.disableFontFace; const disableStream = src.disableStream === true;
} const disableAutoFetch = src.disableAutoFetch === true;
if ( const pdfBug = src.pdfBug === true;
typeof params.ownerDocument !== "object" ||
params.ownerDocument === null
) {
params.ownerDocument = globalThis.document;
}
if (typeof params.disableRange !== "boolean") { // Parameters whose default values depend on other parameters.
params.disableRange = false; const length = rangeTransport ? rangeTransport.length : src.length ?? NaN;
} const useSystemFonts =
if (typeof params.disableStream !== "boolean") { typeof src.useSystemFonts === "boolean"
params.disableStream = false; ? src.useSystemFonts
} : !isNodeJS && !disableFontFace;
if (typeof params.disableAutoFetch !== "boolean") { const useWorkerFetch =
params.disableAutoFetch = false; typeof src.useWorkerFetch === "boolean"
} ? src.useWorkerFetch
: (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
(CMapReaderFactory === DOMCMapReaderFactory &&
StandardFontDataFactory === DOMStandardFontDataFactory &&
isValidFetchUrl(cMapUrl, document.baseURI) &&
isValidFetchUrl(standardFontDataUrl, document.baseURI));
// Parameters only intended for development/testing purposes.
const styleElement =
typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")
? src.styleElement
: null;
// Set the main-thread verbosity level. // Set the main-thread verbosity level.
setVerbosityLevel(params.verbosity); setVerbosityLevel(verbosity);
// Ensure that the various factories can be initialized, when necessary,
// since the user may provide *custom* ones.
const transportFactory = useWorkerFetch
? null
: {
cMapReaderFactory: new CMapReaderFactory({
baseUrl: cMapUrl,
isCompressed: cMapPacked,
}),
standardFontDataFactory: new StandardFontDataFactory({
baseUrl: standardFontDataUrl,
}),
};
if (!worker) { if (!worker) {
const workerParams = { const workerParams = {
verbosity: params.verbosity, verbosity,
port: GlobalWorkerOptions.workerPort, port: GlobalWorkerOptions.workerPort,
}; };
// Worker was not provided -- creating and owning our own. If message port // Worker was not provided -- creating and owning our own. If message port
@ -435,44 +374,78 @@ function getDocument(src) {
task._worker = worker; task._worker = worker;
} }
const docId = task.docId; const docId = task.docId;
const fetchDocParams = {
docId,
apiVersion:
typeof PDFJSDev !== "undefined" && !PDFJSDev.test("TESTING")
? PDFJSDev.eval("BUNDLE_VERSION")
: null,
data,
password,
disableAutoFetch,
rangeChunkSize,
length,
docBaseUrl,
enableXfa,
evaluatorOptions: {
maxImageSize,
disableFontFace,
ignoreErrors,
isEvalSupported,
isOffscreenCanvasSupported,
fontExtraProperties,
useSystemFonts,
cMapUrl: useWorkerFetch ? cMapUrl : null,
standardFontDataUrl: useWorkerFetch ? standardFontDataUrl : null,
},
};
const transportParams = {
ignoreErrors,
isEvalSupported,
disableFontFace,
fontExtraProperties,
enableXfa,
ownerDocument,
disableAutoFetch,
pdfBug,
styleElement,
};
worker.promise worker.promise
.then(function () { .then(function () {
if (task.destroyed) { if (task.destroyed) {
throw new Error("Loading aborted"); throw new Error("Loading aborted");
} }
const workerIdPromise = _fetchDocument( const workerIdPromise = _fetchDocument(worker, fetchDocParams);
worker,
params,
rangeTransport,
docId
);
const networkStreamPromise = new Promise(function (resolve) { const networkStreamPromise = new Promise(function (resolve) {
let networkStream; let networkStream;
if (rangeTransport) { if (rangeTransport) {
networkStream = new PDFDataTransportStream( networkStream = new PDFDataTransportStream(
{ {
length: params.length, length,
initialData: params.initialData, initialData: rangeTransport.initialData,
progressiveDone: params.progressiveDone, progressiveDone: rangeTransport.progressiveDone,
contentDispositionFilename: params.contentDispositionFilename, contentDispositionFilename:
disableRange: params.disableRange, rangeTransport.contentDispositionFilename,
disableStream: params.disableStream, disableRange,
disableStream,
}, },
rangeTransport rangeTransport
); );
} else if (!params.data) { } else if (!data) {
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) { if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
throw new Error("Not implemented: createPDFNetworkStream"); throw new Error("Not implemented: createPDFNetworkStream");
} }
networkStream = createPDFNetworkStream({ networkStream = createPDFNetworkStream({
url: params.url, url,
length: params.length, length,
httpHeaders: params.httpHeaders, httpHeaders,
withCredentials: params.withCredentials, withCredentials,
rangeChunkSize: params.rangeChunkSize, rangeChunkSize,
disableRange: params.disableRange, disableRange,
disableStream: params.disableStream, disableStream,
}); });
} }
resolve(networkStream); resolve(networkStream);
@ -493,7 +466,8 @@ function getDocument(src) {
messageHandler, messageHandler,
task, task,
networkStream, networkStream,
params transportParams,
transportFactory
); );
task._transport = transport; task._transport = transport;
messageHandler.send("Ready", null); messageHandler.send("Ready", null);
@ -510,57 +484,18 @@ function getDocument(src) {
* *
* @param {PDFWorker} worker * @param {PDFWorker} worker
* @param {Object} source * @param {Object} source
* @param {PDFDataRangeTransport} pdfDataRangeTransport
* @param {string} docId - Unique document ID, used in `MessageHandler`.
* @returns {Promise<string>} A promise that is resolved when the worker ID of * @returns {Promise<string>} A promise that is resolved when the worker ID of
* the `MessageHandler` is known. * the `MessageHandler` is known.
* @private * @private
*/ */
async function _fetchDocument(worker, source, pdfDataRangeTransport, docId) { async function _fetchDocument(worker, source) {
if (worker.destroyed) { if (worker.destroyed) {
throw new Error("Worker was destroyed"); throw new Error("Worker was destroyed");
} }
if (pdfDataRangeTransport) {
source.length = pdfDataRangeTransport.length;
source.initialData = pdfDataRangeTransport.initialData;
source.progressiveDone = pdfDataRangeTransport.progressiveDone;
source.contentDispositionFilename =
pdfDataRangeTransport.contentDispositionFilename;
}
const transfers = source.data ? [source.data.buffer] : null;
const workerId = await worker.messageHandler.sendWithPromise( const workerId = await worker.messageHandler.sendWithPromise(
"GetDocRequest", "GetDocRequest",
// Only send the required properties, and *not* the entire `source` object. source,
{ source.data ? [source.data.buffer] : null
docId,
apiVersion:
typeof PDFJSDev !== "undefined" && !PDFJSDev.test("TESTING")
? PDFJSDev.eval("BUNDLE_VERSION")
: null,
data: source.data,
password: source.password,
disableAutoFetch: source.disableAutoFetch,
rangeChunkSize: source.rangeChunkSize,
length: source.length,
docBaseUrl: source.docBaseUrl,
enableXfa: source.enableXfa,
evaluatorOptions: {
maxImageSize: source.maxImageSize,
disableFontFace: source.disableFontFace,
ignoreErrors: source.ignoreErrors,
isEvalSupported: source.isEvalSupported,
isOffscreenCanvasSupported: source.isOffscreenCanvasSupported,
fontExtraProperties: source.fontExtraProperties,
useSystemFonts: source.useSystemFonts,
cMapUrl: source.useWorkerFetch ? source.cMapUrl : null,
standardFontDataUrl: source.useWorkerFetch
? source.standardFontDataUrl
: null,
},
},
transfers
); );
if (worker.destroyed) { if (worker.destroyed) {
@ -569,6 +504,63 @@ async function _fetchDocument(worker, source, pdfDataRangeTransport, docId) {
return workerId; return workerId;
} }
function getUrlProp(val) {
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
return null; // The 'url' is unused with `PDFDataRangeTransport`.
} else if (val instanceof URL) {
return val.href;
}
try {
// The full path is required in the 'url' field.
return new URL(val, window.location).href;
} catch (ex) {
if (
typeof PDFJSDev !== "undefined" &&
PDFJSDev.test("GENERIC") &&
isNodeJS &&
typeof val === "string"
) {
return val; // Use the url as-is in Node.js environments.
}
}
throw new Error(
"Invalid PDF url data: " +
"either string or URL-object is expected in the url property."
);
}
function getDataProp(val) {
// Converting string or array-like data to Uint8Array.
if (
typeof PDFJSDev !== "undefined" &&
PDFJSDev.test("GENERIC") &&
isNodeJS &&
typeof Buffer !== "undefined" && // eslint-disable-line no-undef
val instanceof Buffer // eslint-disable-line no-undef
) {
return new Uint8Array(val);
} else if (
val instanceof Uint8Array &&
val.byteLength === val.buffer.byteLength
) {
// Use the data as-is when it's already a Uint8Array that completely
// "utilizes" its underlying ArrayBuffer, to prevent any possible
// issues when transferring it to the worker-thread.
return val;
} else if (typeof val === "string") {
return stringToBytes(val);
} else if (
(typeof val === "object" && !isNaN(val?.length)) ||
isArrayBuffer(val)
) {
return new Uint8Array(val);
}
throw new Error(
"Invalid PDF binary data: either TypedArray, " +
"string, or array-like object is expected in the data property."
);
}
/** /**
* @typedef {Object} OnProgressParameters * @typedef {Object} OnProgressParameters
* @property {number} loaded - Currently loaded number of bytes. * @property {number} loaded - Currently loaded number of bytes.
@ -2343,7 +2335,7 @@ class WorkerTransport {
#pagePromises = new Map(); #pagePromises = new Map();
constructor(messageHandler, loadingTask, networkStream, params) { constructor(messageHandler, loadingTask, networkStream, params, factory) {
this.messageHandler = messageHandler; this.messageHandler = messageHandler;
this.loadingTask = loadingTask; this.loadingTask = loadingTask;
this.commonObjs = new PDFObjects(); this.commonObjs = new PDFObjects();
@ -2354,15 +2346,8 @@ class WorkerTransport {
}); });
this._params = params; this._params = params;
if (!params.useWorkerFetch) { this.cMapReaderFactory = factory?.cMapReaderFactory;
this.CMapReaderFactory = new params.CMapReaderFactory({ this.standardFontDataFactory = factory?.standardFontDataFactory;
baseUrl: params.cMapUrl,
isCompressed: params.cMapPacked,
});
this.StandardFontDataFactory = new params.StandardFontDataFactory({
baseUrl: params.standardFontDataUrl,
});
}
this.destroyed = false; this.destroyed = false;
this.destroyCapability = null; this.destroyCapability = null;
@ -2830,28 +2815,28 @@ class WorkerTransport {
if (this.destroyed) { if (this.destroyed) {
return Promise.reject(new Error("Worker was destroyed.")); return Promise.reject(new Error("Worker was destroyed."));
} }
if (!this.CMapReaderFactory) { if (!this.cMapReaderFactory) {
return Promise.reject( return Promise.reject(
new Error( new Error(
"CMapReaderFactory not initialized, see the `useWorkerFetch` parameter." "CMapReaderFactory not initialized, see the `useWorkerFetch` parameter."
) )
); );
} }
return this.CMapReaderFactory.fetch(data); return this.cMapReaderFactory.fetch(data);
}); });
messageHandler.on("FetchStandardFontData", data => { messageHandler.on("FetchStandardFontData", data => {
if (this.destroyed) { if (this.destroyed) {
return Promise.reject(new Error("Worker was destroyed.")); return Promise.reject(new Error("Worker was destroyed."));
} }
if (!this.StandardFontDataFactory) { if (!this.standardFontDataFactory) {
return Promise.reject( return Promise.reject(
new Error( new Error(
"StandardFontDataFactory not initialized, see the `useWorkerFetch` parameter." "StandardFontDataFactory not initialized, see the `useWorkerFetch` parameter."
) )
); );
} }
return this.StandardFontDataFactory.fetch(data); return this.standardFontDataFactory.fetch(data);
}); });
} }
@ -3079,10 +3064,10 @@ class WorkerTransport {
} }
get loadingParams() { get loadingParams() {
const params = this._params; const { disableAutoFetch, enableXfa } = this._params;
return shadow(this, "loadingParams", { return shadow(this, "loadingParams", {
disableAutoFetch: params.disableAutoFetch, disableAutoFetch,
enableXfa: params.enableXfa, enableXfa,
}); });
} }
} }

View File

@ -932,12 +932,10 @@ const PDFViewerApplication = {
) { ) {
// The Firefox built-in viewer always calls `setTitleUsingUrl`, before // The Firefox built-in viewer always calls `setTitleUsingUrl`, before
// `initPassiveLoading`, and it never provides an `originalUrl` here. // `initPassiveLoading`, and it never provides an `originalUrl` here.
if (args.originalUrl) { this.setTitleUsingUrl(
this.setTitleUsingUrl(args.originalUrl, /* downloadUrl = */ args.url); args.originalUrl || args.url,
delete args.originalUrl; /* downloadUrl = */ args.url
} else { );
this.setTitleUsingUrl(args.url, /* downloadUrl = */ args.url);
}
} }
// Set the necessary API parameters, using all the available options. // Set the necessary API parameters, using all the available options.
const apiParams = AppOptions.getAll(OptionKind.API); const apiParams = AppOptions.getAll(OptionKind.API);