mirror of
https://github.com/zen-browser/pdf.js.git
synced 2025-07-08 09:20:06 +02:00
Validate explicit destinations on the worker-thread to prevent DataCloneError
(issue 17981)
*Note:* This borrows a helper function from the viewer, however the code cannot be directly shared since the worker-thread has access to various primitives.
This commit is contained in:
parent
522af265a7
commit
7206d0a237
5 changed files with 81 additions and 34 deletions
|
@ -52,11 +52,57 @@ import { GlobalImageCache } from "./image_utils.js";
|
||||||
import { MetadataParser } from "./metadata_parser.js";
|
import { MetadataParser } from "./metadata_parser.js";
|
||||||
import { StructTreeRoot } from "./struct_tree.js";
|
import { StructTreeRoot } from "./struct_tree.js";
|
||||||
|
|
||||||
function fetchDestination(dest) {
|
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;
|
||||||
|
}
|
||||||
|
let allowNull = true;
|
||||||
|
switch (zoom.name) {
|
||||||
|
case "XYZ":
|
||||||
|
if (args.length !== 3) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Fit":
|
||||||
|
case "FitB":
|
||||||
|
return args.length === 0;
|
||||||
|
case "FitH":
|
||||||
|
case "FitBH":
|
||||||
|
case "FitV":
|
||||||
|
case "FitBV":
|
||||||
|
if (args.length !== 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "FitR":
|
||||||
|
if (args.length !== 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchDest(dest) {
|
||||||
if (dest instanceof Dict) {
|
if (dest instanceof Dict) {
|
||||||
dest = dest.get("D");
|
dest = dest.get("D");
|
||||||
}
|
}
|
||||||
return Array.isArray(dest) ? dest : null;
|
return isValidExplicitDest(dest) ? dest : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchRemoteDest(action) {
|
function fetchRemoteDest(action) {
|
||||||
|
@ -67,7 +113,7 @@ function fetchRemoteDest(action) {
|
||||||
}
|
}
|
||||||
if (typeof dest === "string") {
|
if (typeof dest === "string") {
|
||||||
return stringToPDFString(dest);
|
return stringToPDFString(dest);
|
||||||
} else if (Array.isArray(dest)) {
|
} else if (isValidExplicitDest(dest)) {
|
||||||
return JSON.stringify(dest);
|
return JSON.stringify(dest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -641,14 +687,14 @@ class Catalog {
|
||||||
dests = Object.create(null);
|
dests = Object.create(null);
|
||||||
if (obj instanceof NameTree) {
|
if (obj instanceof NameTree) {
|
||||||
for (const [key, value] of obj.getAll()) {
|
for (const [key, value] of obj.getAll()) {
|
||||||
const dest = fetchDestination(value);
|
const dest = fetchDest(value);
|
||||||
if (dest) {
|
if (dest) {
|
||||||
dests[stringToPDFString(key)] = dest;
|
dests[stringToPDFString(key)] = dest;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (obj instanceof Dict) {
|
} else if (obj instanceof Dict) {
|
||||||
obj.forEach(function (key, value) {
|
obj.forEach(function (key, value) {
|
||||||
const dest = fetchDestination(value);
|
const dest = fetchDest(value);
|
||||||
if (dest) {
|
if (dest) {
|
||||||
dests[key] = dest;
|
dests[key] = dest;
|
||||||
}
|
}
|
||||||
|
@ -660,7 +706,7 @@ class Catalog {
|
||||||
getDestination(id) {
|
getDestination(id) {
|
||||||
const obj = this._readDests();
|
const obj = this._readDests();
|
||||||
if (obj instanceof NameTree) {
|
if (obj instanceof NameTree) {
|
||||||
const dest = fetchDestination(obj.get(id));
|
const dest = fetchDest(obj.get(id));
|
||||||
if (dest) {
|
if (dest) {
|
||||||
return dest;
|
return dest;
|
||||||
}
|
}
|
||||||
|
@ -672,7 +718,7 @@ class Catalog {
|
||||||
return allDest;
|
return allDest;
|
||||||
}
|
}
|
||||||
} else if (obj instanceof Dict) {
|
} else if (obj instanceof Dict) {
|
||||||
const dest = fetchDestination(obj.get(id));
|
const dest = fetchDest(obj.get(id));
|
||||||
if (dest) {
|
if (dest) {
|
||||||
return dest;
|
return dest;
|
||||||
}
|
}
|
||||||
|
@ -1703,7 +1749,7 @@ class Catalog {
|
||||||
}
|
}
|
||||||
if (typeof dest === "string") {
|
if (typeof dest === "string") {
|
||||||
resultObj.dest = stringToPDFString(dest);
|
resultObj.dest = stringToPDFString(dest);
|
||||||
} else if (Array.isArray(dest)) {
|
} else if (isValidExplicitDest(dest)) {
|
||||||
resultObj.dest = dest;
|
resultObj.dest = dest;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2933,10 +2933,9 @@ class WorkerTransport {
|
||||||
getPageIndex(ref) {
|
getPageIndex(ref) {
|
||||||
if (
|
if (
|
||||||
typeof ref !== "object" ||
|
typeof ref !== "object" ||
|
||||||
ref === null ||
|
!Number.isInteger(ref?.num) ||
|
||||||
!Number.isInteger(ref.num) ||
|
|
||||||
ref.num < 0 ||
|
ref.num < 0 ||
|
||||||
!Number.isInteger(ref.gen) ||
|
!Number.isInteger(ref?.gen) ||
|
||||||
ref.gen < 0
|
ref.gen < 0
|
||||||
) {
|
) {
|
||||||
return Promise.reject(new Error("Invalid pageIndex request."));
|
return Promise.reject(new Error("Invalid pageIndex request."));
|
||||||
|
|
1
test/pdfs/issue17981.pdf.link
Normal file
1
test/pdfs/issue17981.pdf.link
Normal file
|
@ -0,0 +1 @@
|
||||||
|
https://github.com/mozilla/pdf.js/files/15061082/unhandled_get_annotations.pdf
|
|
@ -2111,6 +2111,16 @@
|
||||||
"lastPage": 1,
|
"lastPage": 1,
|
||||||
"type": "eq"
|
"type": "eq"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "issue17981",
|
||||||
|
"file": "pdfs/issue17981.pdf",
|
||||||
|
"md5": "3fd6d2bdcad8ff7cc5c0575da0b70266",
|
||||||
|
"rounds": 1,
|
||||||
|
"link": true,
|
||||||
|
"lastPage": 1,
|
||||||
|
"type": "eq",
|
||||||
|
"annotations": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "issue9105_other",
|
"id": "issue9105_other",
|
||||||
"file": "pdfs/issue9105_other.pdf",
|
"file": "pdfs/issue9105_other.pdf",
|
||||||
|
|
|
@ -454,10 +454,7 @@ class PDFLinkService {
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
if (
|
if (typeof dest === "string" || PDFLinkService.#isValidExplicitDest(dest)) {
|
||||||
typeof dest === "string" ||
|
|
||||||
PDFLinkService.#isValidExplicitDestination(dest)
|
|
||||||
) {
|
|
||||||
this.goToDestination(dest);
|
this.goToDestination(dest);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -549,49 +546,44 @@ class PDFLinkService {
|
||||||
return this.#pagesRefCache.get(refStr) || null;
|
return this.#pagesRefCache.get(refStr) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static #isValidExplicitDestination(dest) {
|
static #isValidExplicitDest(dest) {
|
||||||
if (!Array.isArray(dest)) {
|
if (!Array.isArray(dest) || dest.length < 2) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const destLength = dest.length;
|
const [page, zoom, ...args] = dest;
|
||||||
if (destLength < 2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const page = dest[0];
|
|
||||||
if (
|
if (
|
||||||
!(
|
!(
|
||||||
typeof page === "object" &&
|
typeof page === "object" &&
|
||||||
Number.isInteger(page.num) &&
|
Number.isInteger(page?.num) &&
|
||||||
Number.isInteger(page.gen)
|
Number.isInteger(page?.gen)
|
||||||
) &&
|
) &&
|
||||||
!(Number.isInteger(page) && page >= 0)
|
!Number.isInteger(page)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const zoom = dest[1];
|
if (!(typeof zoom === "object" && typeof zoom?.name === "string")) {
|
||||||
if (!(typeof zoom === "object" && typeof zoom.name === "string")) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let allowNull = true;
|
let allowNull = true;
|
||||||
switch (zoom.name) {
|
switch (zoom.name) {
|
||||||
case "XYZ":
|
case "XYZ":
|
||||||
if (destLength !== 5) {
|
if (args.length !== 3) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "Fit":
|
case "Fit":
|
||||||
case "FitB":
|
case "FitB":
|
||||||
return destLength === 2;
|
return args.length === 0;
|
||||||
case "FitH":
|
case "FitH":
|
||||||
case "FitBH":
|
case "FitBH":
|
||||||
case "FitV":
|
case "FitV":
|
||||||
case "FitBV":
|
case "FitBV":
|
||||||
if (destLength !== 3) {
|
if (args.length !== 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "FitR":
|
case "FitR":
|
||||||
if (destLength !== 6) {
|
if (args.length !== 4) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
allowNull = false;
|
allowNull = false;
|
||||||
|
@ -599,9 +591,8 @@ class PDFLinkService {
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (let i = 2; i < destLength; i++) {
|
for (const arg of args) {
|
||||||
const param = dest[i];
|
if (!(typeof arg === "number" || (allowNull && arg === null))) {
|
||||||
if (!(typeof param === "number" || (allowNull && param === null))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue