From 165d90fe262994423a82a44f9e37b57b72727828 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Fri, 28 Feb 2025 14:54:16 +0100 Subject: [PATCH] Re-use the `isValidExplicitDest` helper function in the worker/viewer Currently we re-implement the same helper function twice, which in hindsight seems like the wrong decision since that way it's quite easy for the implementations to accidentally diverge. The reason for doing it this way was because the code in the worker-thread is able to check for `Ref`- and `Name`-instances directly, which obviously isn't possible in the viewer but can be solved by passing validation-functions to the helper. --- src/core/catalog.js | 72 ++++++++++------------------------------- src/display/api.js | 25 ++++++++------ src/pdf.js | 2 ++ src/shared/util.js | 49 ++++++++++++++++++++++++++++ test/unit/pdf_spec.js | 2 ++ web/pdf_link_service.js | 57 ++------------------------------ web/pdfjs.js | 2 ++ 7 files changed, 90 insertions(+), 119 deletions(-) diff --git a/src/core/catalog.js b/src/core/catalog.js index 84624b863dbf5..cf4c4611f2771 100644 --- a/src/core/catalog.js +++ b/src/core/catalog.js @@ -14,15 +14,7 @@ */ import { - collectActions, - isNumberArray, - MissingDataException, - PDF_VERSION_REGEXP, - recoverJsURL, - toRomanNumerals, - XRefEntryException, -} from "./core_utils.js"; -import { + _isValidExplicitDest, createValidAbsoluteUrl, DocumentActionEventType, FormatError, @@ -34,6 +26,15 @@ import { stringToUTF8String, warn, } from "../shared/util.js"; +import { + collectActions, + isNumberArray, + MissingDataException, + PDF_VERSION_REGEXP, + recoverJsURL, + toRomanNumerals, + XRefEntryException, +} from "./core_utils.js"; import { Dict, isDict, @@ -53,52 +54,13 @@ import { GlobalImageCache } from "./image_utils.js"; import { MetadataParser } from "./metadata_parser.js"; import { StructTreeRoot } from "./struct_tree.js"; -function isValidExplicitDest(dest) { - if (!Array.isArray(dest) || dest.length < 2) { - return false; - } - const [page, zoom, ...args] = dest; - if (!(page instanceof Ref) && !Number.isInteger(page)) { - return false; - } - if (!(zoom instanceof Name)) { - return false; - } - const argsLen = args.length; - let allowNull = true; - switch (zoom.name) { - case "XYZ": - if (argsLen < 2 || argsLen > 3) { - return false; - } - break; - case "Fit": - case "FitB": - return argsLen === 0; - case "FitH": - case "FitBH": - case "FitV": - case "FitBV": - if (argsLen > 1) { - return false; - } - break; - case "FitR": - if (argsLen !== 4) { - return false; - } - allowNull = false; - break; - default: - return false; - } - for (const arg of args) { - if (!(typeof arg === "number" || (allowNull && arg === null))) { - return false; - } - } - return true; -} +const isRef = v => v instanceof Ref; + +const isValidExplicitDest = _isValidExplicitDest.bind( + null, + /* validRef = */ isRef, + /* validName = */ isName +); function fetchDest(dest) { if (dest instanceof Dict) { diff --git a/src/display/api.js b/src/display/api.js index d5aff8bc9d09a..ce9ae3c7c8031 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -18,6 +18,7 @@ */ import { + _isValidExplicitDest, AbortException, AnnotationMode, assert, @@ -595,15 +596,20 @@ function getFactoryUrlProp(val) { throw new Error(`Invalid factory url: "${val}" must include trailing slash.`); } -function isRefProxy(ref) { - return ( - typeof ref === "object" && - Number.isInteger(ref?.num) && - ref.num >= 0 && - Number.isInteger(ref?.gen) && - ref.gen >= 0 - ); -} +const isRefProxy = v => + typeof v === "object" && + Number.isInteger(v?.num) && + v.num >= 0 && + Number.isInteger(v?.gen) && + v.gen >= 0; + +const isNameProxy = v => typeof v === "object" && typeof v?.name === "string"; + +const isValidExplicitDest = _isValidExplicitDest.bind( + null, + /* validRef = */ isRefProxy, + /* validName = */ isNameProxy +); /** * @typedef {Object} OnProgressParameters @@ -3484,6 +3490,7 @@ const build = export { build, getDocument, + isValidExplicitDest, LoopbackPort, PDFDataRangeTransport, PDFDocumentLoadingTask, diff --git a/src/pdf.js b/src/pdf.js index 351e5cfed42d3..eea401cd6c893 100644 --- a/src/pdf.js +++ b/src/pdf.js @@ -45,6 +45,7 @@ import { import { build, getDocument, + isValidExplicitDest, PDFDataRangeTransport, PDFWorker, version, @@ -117,6 +118,7 @@ export { InvalidPDFException, isDataScheme, isPdfFile, + isValidExplicitDest, noContextMenu, normalizeUnicode, OPS, diff --git a/src/shared/util.js b/src/shared/util.js index baf53a1b0ff14..5775c0d4bfb6e 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -1080,6 +1080,54 @@ function getUuid() { const AnnotationPrefix = "pdfjs_internal_id_"; +function _isValidExplicitDest(validRef, validName, dest) { + if (!Array.isArray(dest) || dest.length < 2) { + return false; + } + const [page, zoom, ...args] = dest; + if (!validRef(page) && !Number.isInteger(page)) { + return false; + } + if (!validName(zoom)) { + return false; + } + const argsLen = args.length; + let allowNull = true; + switch (zoom.name) { + case "XYZ": + if (argsLen < 2 || argsLen > 3) { + return false; + } + break; + case "Fit": + case "FitB": + return argsLen === 0; + case "FitH": + case "FitBH": + case "FitV": + case "FitBV": + if (argsLen > 1) { + return false; + } + break; + case "FitR": + if (argsLen !== 4) { + return false; + } + allowNull = false; + break; + default: + return false; + } + for (const arg of args) { + if (typeof arg === "number" || (allowNull && arg === null)) { + continue; + } + return false; + } + return true; +} + // TODO: Remove this once `Uint8Array.prototype.toHex` is generally available. function toHexUtil(arr) { if (Uint8Array.prototype.toHex) { @@ -1119,6 +1167,7 @@ if ( } export { + _isValidExplicitDest, AbortException, AnnotationActionEventType, AnnotationBorderStyleType, diff --git a/test/unit/pdf_spec.js b/test/unit/pdf_spec.js index 66b85d4d96519..95267085ea6cf 100644 --- a/test/unit/pdf_spec.js +++ b/test/unit/pdf_spec.js @@ -36,6 +36,7 @@ import { import { build, getDocument, + isValidExplicitDest, PDFDataRangeTransport, PDFWorker, version, @@ -94,6 +95,7 @@ const expectedAPI = Object.freeze({ InvalidPDFException, isDataScheme, isPdfFile, + isValidExplicitDest, noContextMenu, normalizeUnicode, OPS, diff --git a/web/pdf_link_service.js b/web/pdf_link_service.js index 14ffb9c10134b..e22243639abc1 100644 --- a/web/pdf_link_service.js +++ b/web/pdf_link_service.js @@ -16,6 +16,7 @@ /** @typedef {import("./event_utils").EventBus} EventBus */ /** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */ +import { isValidExplicitDest } from "pdfjs-lib"; import { parseQueryString } from "./ui_utils.js"; const DEFAULT_LINK_REL = "noopener noreferrer nofollow"; @@ -415,7 +416,7 @@ class PDFLinkService { } } catch {} - if (typeof dest === "string" || PDFLinkService.#isValidExplicitDest(dest)) { + if (typeof dest === "string" || isValidExplicitDest(dest)) { this.goToDestination(dest); return; } @@ -486,60 +487,6 @@ class PDFLinkService { optionalContentConfig ); } - - static #isValidExplicitDest(dest) { - if (!Array.isArray(dest) || dest.length < 2) { - return false; - } - const [page, zoom, ...args] = dest; - if ( - !( - typeof page === "object" && - Number.isInteger(page?.num) && - Number.isInteger(page?.gen) - ) && - !Number.isInteger(page) - ) { - return false; - } - if (!(typeof zoom === "object" && typeof zoom?.name === "string")) { - return false; - } - const argsLen = args.length; - let allowNull = true; - switch (zoom.name) { - case "XYZ": - if (argsLen < 2 || argsLen > 3) { - return false; - } - break; - case "Fit": - case "FitB": - return argsLen === 0; - case "FitH": - case "FitBH": - case "FitV": - case "FitBV": - if (argsLen > 1) { - return false; - } - break; - case "FitR": - if (argsLen !== 4) { - return false; - } - allowNull = false; - break; - default: - return false; - } - for (const arg of args) { - if (!(typeof arg === "number" || (allowNull && arg === null))) { - return false; - } - } - return true; - } } /** diff --git a/web/pdfjs.js b/web/pdfjs.js index 47739ef5223a4..2c8f7fff7e48f 100644 --- a/web/pdfjs.js +++ b/web/pdfjs.js @@ -39,6 +39,7 @@ const { InvalidPDFException, isDataScheme, isPdfFile, + isValidExplicitDest, noContextMenu, normalizeUnicode, OPS, @@ -90,6 +91,7 @@ export { InvalidPDFException, isDataScheme, isPdfFile, + isValidExplicitDest, noContextMenu, normalizeUnicode, OPS,