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 params = Object.create(null);
let rangeTransport = null,
worker = null;
const url = src.url ? getUrlProp(src.url) : null;
const data = src.data ? getDataProp(src.data) : null;
const httpHeaders = src.httpHeaders || null;
const withCredentials = src.withCredentials === true;
const password = src.password ?? null;
const rangeTransport =
src.range instanceof PDFDataRangeTransport ? src.range : null;
const rangeChunkSize =
Number.isInteger(src.rangeChunkSize) && src.rangeChunkSize > 0
? src.rangeChunkSize
: DEFAULT_RANGE_CHUNK_SIZE;
let worker = src.worker instanceof PDFWorker ? src.worker : null;
const verbosity = src.verbosity;
// 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
// they contain the *entire* PDF document and can thus be arbitrarily long.
const docBaseUrl =
typeof src.docBaseUrl === "string" && !isDataScheme(src.docBaseUrl)
? src.docBaseUrl
: null;
const cMapUrl = typeof src.cMapUrl === "string" ? src.cMapUrl : null;
const cMapPacked = src.cMapPacked !== false;
const CMapReaderFactory = src.CMapReaderFactory || DefaultCMapReaderFactory;
const standardFontDataUrl =
typeof src.standardFontDataUrl === "string"
? src.standardFontDataUrl
: null;
const StandardFontDataFactory =
src.StandardFontDataFactory || DefaultStandardFontDataFactory;
const ignoreErrors = src.stopAtErrors !== true;
const maxImageSize =
Number.isInteger(src.maxImageSize) && src.maxImageSize > -1
? src.maxImageSize
: -1;
const isEvalSupported = src.isEvalSupported !== false;
const isOffscreenCanvasSupported =
typeof src.isOffscreenCanvasSupported === "boolean"
? src.isOffscreenCanvasSupported
: !isNodeJS;
const disableFontFace =
typeof src.disableFontFace === "boolean" ? src.disableFontFace : isNodeJS;
const fontExtraProperties = src.fontExtraProperties === true;
const enableXfa = src.enableXfa === true;
const ownerDocument = src.ownerDocument || globalThis.document;
const disableRange = src.disableRange === true;
const disableStream = src.disableStream === true;
const disableAutoFetch = src.disableAutoFetch === true;
const pdfBug = src.pdfBug === true;
for (const key in src) {
const val = src[key];
// Parameters whose default values depend on other parameters.
const length = rangeTransport ? rangeTransport.length : src.length ?? NaN;
const useSystemFonts =
typeof src.useSystemFonts === "boolean"
? src.useSystemFonts
: !isNodeJS && !disableFontFace;
const useWorkerFetch =
typeof src.useWorkerFetch === "boolean"
? src.useWorkerFetch
: (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
(CMapReaderFactory === DOMCMapReaderFactory &&
StandardFontDataFactory === DOMStandardFontDataFactory &&
isValidFetchUrl(cMapUrl, document.baseURI) &&
isValidFetchUrl(standardFontDataUrl, document.baseURI));
switch (key) {
case "url":
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
continue; // The 'url' is unused with `PDFDataRangeTransport`.
} else {
if (val instanceof URL) {
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
// 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.
params.docBaseUrl = null;
}
if (!Number.isInteger(params.maxImageSize) || params.maxImageSize < -1) {
params.maxImageSize = -1;
}
if (typeof params.cMapUrl !== "string") {
params.cMapUrl = null;
}
if (typeof params.standardFontDataUrl !== "string") {
params.standardFontDataUrl = null;
}
if (typeof params.useWorkerFetch !== "boolean") {
params.useWorkerFetch =
(typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
(params.CMapReaderFactory === DOMCMapReaderFactory &&
params.StandardFontDataFactory === DOMStandardFontDataFactory &&
isValidFetchUrl(params.cMapUrl, document.baseURI) &&
isValidFetchUrl(params.standardFontDataUrl, document.baseURI));
}
if (typeof params.isEvalSupported !== "boolean") {
params.isEvalSupported = true;
}
if (typeof params.isOffscreenCanvasSupported !== "boolean") {
params.isOffscreenCanvasSupported = !isNodeJS;
}
if (typeof params.disableFontFace !== "boolean") {
params.disableFontFace = isNodeJS;
}
if (typeof params.useSystemFonts !== "boolean") {
params.useSystemFonts = !isNodeJS && !params.disableFontFace;
}
if (
typeof params.ownerDocument !== "object" ||
params.ownerDocument === null
) {
params.ownerDocument = globalThis.document;
}
if (typeof params.disableRange !== "boolean") {
params.disableRange = false;
}
if (typeof params.disableStream !== "boolean") {
params.disableStream = false;
}
if (typeof params.disableAutoFetch !== "boolean") {
params.disableAutoFetch = false;
}
// Parameters only intended for development/testing purposes.
const styleElement =
typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")
? src.styleElement
: null;
// 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) {
const workerParams = {
verbosity: params.verbosity,
verbosity,
port: GlobalWorkerOptions.workerPort,
};
// Worker was not provided -- creating and owning our own. If message port
@ -435,44 +374,78 @@ function getDocument(src) {
task._worker = worker;
}
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
.then(function () {
if (task.destroyed) {
throw new Error("Loading aborted");
}
const workerIdPromise = _fetchDocument(
worker,
params,
rangeTransport,
docId
);
const workerIdPromise = _fetchDocument(worker, fetchDocParams);
const networkStreamPromise = new Promise(function (resolve) {
let networkStream;
if (rangeTransport) {
networkStream = new PDFDataTransportStream(
{
length: params.length,
initialData: params.initialData,
progressiveDone: params.progressiveDone,
contentDispositionFilename: params.contentDispositionFilename,
disableRange: params.disableRange,
disableStream: params.disableStream,
length,
initialData: rangeTransport.initialData,
progressiveDone: rangeTransport.progressiveDone,
contentDispositionFilename:
rangeTransport.contentDispositionFilename,
disableRange,
disableStream,
},
rangeTransport
);
} else if (!params.data) {
} else if (!data) {
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
throw new Error("Not implemented: createPDFNetworkStream");
}
networkStream = createPDFNetworkStream({
url: params.url,
length: params.length,
httpHeaders: params.httpHeaders,
withCredentials: params.withCredentials,
rangeChunkSize: params.rangeChunkSize,
disableRange: params.disableRange,
disableStream: params.disableStream,
url,
length,
httpHeaders,
withCredentials,
rangeChunkSize,
disableRange,
disableStream,
});
}
resolve(networkStream);
@ -493,7 +466,8 @@ function getDocument(src) {
messageHandler,
task,
networkStream,
params
transportParams,
transportFactory
);
task._transport = transport;
messageHandler.send("Ready", null);
@ -510,57 +484,18 @@ function getDocument(src) {
*
* @param {PDFWorker} worker
* @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
* the `MessageHandler` is known.
* @private
*/
async function _fetchDocument(worker, source, pdfDataRangeTransport, docId) {
async function _fetchDocument(worker, source) {
if (worker.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(
"GetDocRequest",
// Only send the required properties, and *not* the entire `source` object.
{
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
source,
source.data ? [source.data.buffer] : null
);
if (worker.destroyed) {
@ -569,6 +504,63 @@ async function _fetchDocument(worker, source, pdfDataRangeTransport, docId) {
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
* @property {number} loaded - Currently loaded number of bytes.
@ -2343,7 +2335,7 @@ class WorkerTransport {
#pagePromises = new Map();
constructor(messageHandler, loadingTask, networkStream, params) {
constructor(messageHandler, loadingTask, networkStream, params, factory) {
this.messageHandler = messageHandler;
this.loadingTask = loadingTask;
this.commonObjs = new PDFObjects();
@ -2354,15 +2346,8 @@ class WorkerTransport {
});
this._params = params;
if (!params.useWorkerFetch) {
this.CMapReaderFactory = new params.CMapReaderFactory({
baseUrl: params.cMapUrl,
isCompressed: params.cMapPacked,
});
this.StandardFontDataFactory = new params.StandardFontDataFactory({
baseUrl: params.standardFontDataUrl,
});
}
this.cMapReaderFactory = factory?.cMapReaderFactory;
this.standardFontDataFactory = factory?.standardFontDataFactory;
this.destroyed = false;
this.destroyCapability = null;
@ -2830,28 +2815,28 @@ class WorkerTransport {
if (this.destroyed) {
return Promise.reject(new Error("Worker was destroyed."));
}
if (!this.CMapReaderFactory) {
if (!this.cMapReaderFactory) {
return Promise.reject(
new Error(
"CMapReaderFactory not initialized, see the `useWorkerFetch` parameter."
)
);
}
return this.CMapReaderFactory.fetch(data);
return this.cMapReaderFactory.fetch(data);
});
messageHandler.on("FetchStandardFontData", data => {
if (this.destroyed) {
return Promise.reject(new Error("Worker was destroyed."));
}
if (!this.StandardFontDataFactory) {
if (!this.standardFontDataFactory) {
return Promise.reject(
new Error(
"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() {
const params = this._params;
const { disableAutoFetch, enableXfa } = this._params;
return shadow(this, "loadingParams", {
disableAutoFetch: params.disableAutoFetch,
enableXfa: params.enableXfa,
disableAutoFetch,
enableXfa,
});
}
}

View File

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