Validate even more dictionary properties

This checks primarily Arrays, but also some other properties, that we'll end up sending (sometimes indirectly) to the main-thread.
This commit is contained in:
Jonas Jenwald 2024-04-29 23:07:41 +02:00
parent 1b811ac113
commit 52f7ff155d
8 changed files with 125 additions and 64 deletions

View file

@ -43,6 +43,7 @@ import {
getInheritableProperty, getInheritableProperty,
getRotationMatrix, getRotationMatrix,
isAscii, isAscii,
isNumberArray,
numberToString, numberToString,
stringToUTF16String, stringToUTF16String,
} from "./core_utils.js"; } from "./core_utils.js";
@ -550,7 +551,7 @@ function getQuadPoints(dict, rect) {
// Each quadrilateral must consist of eight coordinates. // Each quadrilateral must consist of eight coordinates.
const quadPoints = dict.getArray("QuadPoints"); const quadPoints = dict.getArray("QuadPoints");
if ( if (
!Array.isArray(quadPoints) || !isNumberArray(quadPoints, null) ||
quadPoints.length === 0 || quadPoints.length === 0 ||
quadPoints.length % 8 > 0 quadPoints.length % 8 > 0
) { ) {
@ -914,8 +915,7 @@ class Annotation {
* @param {Array} rectangle - The rectangle array with exactly four entries * @param {Array} rectangle - The rectangle array with exactly four entries
*/ */
setRectangle(rectangle) { setRectangle(rectangle) {
this.rectangle = this.rectangle = isNumberArray(rectangle, 4)
Array.isArray(rectangle) && rectangle.length === 4
? Util.normalizeRect(rectangle) ? Util.normalizeRect(rectangle)
: [0, 0, 0, 0]; : [0, 0, 0, 0];
} }
@ -1150,8 +1150,14 @@ class Annotation {
["ExtGState", "ColorSpace", "Pattern", "Shading", "XObject", "Font"], ["ExtGState", "ColorSpace", "Pattern", "Shading", "XObject", "Font"],
appearance appearance
); );
const bbox = appearanceDict.getArray("BBox") || [0, 0, 1, 1]; let bbox = appearanceDict.getArray("BBox");
const matrix = appearanceDict.getArray("Matrix") || [1, 0, 0, 1, 0, 0]; if (!isNumberArray(bbox, 4)) {
bbox = [0, 0, 1, 1];
}
let matrix = appearanceDict.getArray("Matrix");
if (!isNumberArray(matrix, 6)) {
matrix = [1, 0, 0, 1, 0, 0];
}
const transform = getTransformMatrix(rect, bbox, matrix); const transform = getTransformMatrix(rect, bbox, matrix);
const opList = new OperatorList(); const opList = new OperatorList();
@ -1248,10 +1254,19 @@ class Annotation {
if (text.length > 1 || text[0]) { if (text.length > 1 || text[0]) {
const appearanceDict = this.appearance.dict; const appearanceDict = this.appearance.dict;
let bbox = appearanceDict.getArray("BBox");
if (!isNumberArray(bbox, 4)) {
bbox = null;
}
let matrix = appearanceDict.getArray("Matrix");
if (!isNumberArray(matrix, 6)) {
matrix = null;
}
this.data.textPosition = this._transformPoint( this.data.textPosition = this._transformPoint(
firstPosition, firstPosition,
appearanceDict.getArray("BBox"), bbox,
appearanceDict.getArray("Matrix") matrix
); );
this.data.textContent = text; this.data.textContent = text;
} }
@ -1401,7 +1416,7 @@ class AnnotationBorderStyle {
setWidth(width, rect = [0, 0, 0, 0]) { setWidth(width, rect = [0, 0, 0, 0]) {
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
assert( assert(
Array.isArray(rect) && rect.length === 4, isNumberArray(rect, 4),
"A valid `rect` parameter must be provided." "A valid `rect` parameter must be provided."
); );
} }
@ -2972,7 +2987,8 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
: this.uncheckedAppearance; : this.uncheckedAppearance;
if (appearance) { if (appearance) {
const savedAppearance = this.appearance; const savedAppearance = this.appearance;
const savedMatrix = appearance.dict.getArray("Matrix") || IDENTITY_MATRIX; const matrix = appearance.dict.getArray("Matrix");
const savedMatrix = isNumberArray(matrix, 6) ? matrix : IDENTITY_MATRIX;
if (rotation) { if (rotation) {
appearance.dict.set( appearance.dict.set(
@ -3739,8 +3755,7 @@ class PopupAnnotation extends Annotation {
} }
const parentRect = parentItem.getArray("Rect"); const parentRect = parentItem.getArray("Rect");
this.data.parentRect = this.data.parentRect = isNumberArray(parentRect, 4)
Array.isArray(parentRect) && parentRect.length === 4
? Util.normalizeRect(parentRect) ? Util.normalizeRect(parentRect)
: null; : null;
@ -4030,7 +4045,10 @@ class LineAnnotation extends MarkupAnnotation {
this.data.hasOwnCanvas = this.data.noRotate; this.data.hasOwnCanvas = this.data.noRotate;
this.data.noHTML = false; this.data.noHTML = false;
const lineCoordinates = dict.getArray("L"); let lineCoordinates = dict.getArray("L");
if (!isNumberArray(lineCoordinates, 4)) {
lineCoordinates = [0, 0, 0, 0];
}
this.data.lineCoordinates = Util.normalizeRect(lineCoordinates); this.data.lineCoordinates = Util.normalizeRect(lineCoordinates);
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) { if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
@ -4225,7 +4243,7 @@ class PolylineAnnotation extends MarkupAnnotation {
// horizontal and vertical coordinates, respectively, of each vertex. // horizontal and vertical coordinates, respectively, of each vertex.
// Convert this to an array of objects with x and y coordinates. // Convert this to an array of objects with x and y coordinates.
const rawVertices = dict.getArray("Vertices"); const rawVertices = dict.getArray("Vertices");
if (!Array.isArray(rawVertices)) { if (!isNumberArray(rawVertices, null)) {
return; return;
} }
for (let i = 0, ii = rawVertices.length; i < ii; i += 2) { for (let i = 0, ii = rawVertices.length; i < ii; i += 2) {
@ -4314,11 +4332,15 @@ class InkAnnotation extends MarkupAnnotation {
// of each vertex. Convert this to an array of objects with x and y // of each vertex. Convert this to an array of objects with x and y
// coordinates. // coordinates.
this.data.inkLists.push([]); this.data.inkLists.push([]);
if (!Array.isArray(rawInkLists[i])) {
continue;
}
for (let j = 0, jj = rawInkLists[i].length; j < jj; j += 2) { for (let j = 0, jj = rawInkLists[i].length; j < jj; j += 2) {
this.data.inkLists[i].push({ const x = xref.fetchIfRef(rawInkLists[i][j]),
x: xref.fetchIfRef(rawInkLists[i][j]), y = xref.fetchIfRef(rawInkLists[i][j + 1]);
y: xref.fetchIfRef(rawInkLists[i][j + 1]), if (typeof x === "number" && typeof y === "number") {
}); this.data.inkLists[i].push({ x, y });
}
} }
} }

View file

@ -15,6 +15,7 @@
import { import {
collectActions, collectActions,
isNumberArray,
MissingDataException, MissingDataException,
PDF_VERSION_REGEXP, PDF_VERSION_REGEXP,
recoverJsURL, recoverJsURL,
@ -388,8 +389,7 @@ class Catalog {
// We only need to parse the color when it's valid, and non-default. // We only need to parse the color when it's valid, and non-default.
if ( if (
Array.isArray(color) && isNumberArray(color, 3) &&
color.length === 3 &&
(color[0] !== 0 || color[1] !== 0 || color[2] !== 0) (color[0] !== 0 || color[1] !== 0 || color[2] !== 0)
) { ) {
rgbColor = ColorSpace.singletons.rgb.getRgb(color, 0); rgbColor = ColorSpace.singletons.rgb.getRgb(color, 0);

View file

@ -218,6 +218,21 @@ function isWhiteSpace(ch) {
return ch === 0x20 || ch === 0x09 || ch === 0x0d || ch === 0x0a; return ch === 0x20 || ch === 0x09 || ch === 0x0d || ch === 0x0a;
} }
/**
* Checks if something is an Array containing only boolean values,
* and (optionally) checks its length.
* @param {any} arr
* @param {number | null} len
* @returns {boolean}
*/
function isBooleanArray(arr, len) {
return (
Array.isArray(arr) &&
(len === null || arr.length === len) &&
arr.every(x => typeof x === "boolean")
);
}
/** /**
* Checks if something is an Array containing only numbers, * Checks if something is an Array containing only numbers,
* and (optionally) checks its length. * and (optionally) checks its length.
@ -652,6 +667,7 @@ export {
getRotationMatrix, getRotationMatrix,
getSizeInBytes, getSizeInBytes,
isAscii, isAscii,
isBooleanArray,
isNumberArray, isNumberArray,
isWhiteSpace, isWhiteSpace,
log2, log2,

View file

@ -39,6 +39,7 @@ import {
collectActions, collectActions,
getInheritableProperty, getInheritableProperty,
getNewAnnotationsMap, getNewAnnotationsMap,
isNumberArray,
isWhiteSpace, isWhiteSpace,
MissingDataException, MissingDataException,
PDF_VERSION_REGEXP, PDF_VERSION_REGEXP,
@ -162,7 +163,7 @@ class Page {
} }
let box = this._getInheritableProperty(name, /* getArray = */ true); let box = this._getInheritableProperty(name, /* getArray = */ true);
if (Array.isArray(box) && box.length === 4) { if (isNumberArray(box, 4)) {
box = Util.normalizeRect(box); box = Util.normalizeRect(box);
if (box[2] - box[0] > 0 && box[3] - box[1] > 0) { if (box[2] - box[0] > 0 && box[3] - box[1] > 0) {
return box; return box;

View file

@ -460,12 +460,12 @@ class PartialEvaluator {
localColorSpaceCache localColorSpaceCache
) { ) {
const dict = xobj.dict; const dict = xobj.dict;
const matrix = dict.getArray("Matrix"); let matrix = dict.getArray("Matrix");
if (!isNumberArray(matrix, 6)) {
matrix = null;
}
let bbox = dict.getArray("BBox"); let bbox = dict.getArray("BBox");
bbox = bbox = isNumberArray(bbox, 4) ? Util.normalizeRect(bbox) : null;
Array.isArray(bbox) && bbox.length === 4
? Util.normalizeRect(bbox)
: null;
let optionalContent, groupOptions; let optionalContent, groupOptions;
if (dict.has("OC")) { if (dict.has("OC")) {
@ -1578,7 +1578,10 @@ class PartialEvaluator {
localShadingPatternCache, localShadingPatternCache,
}); });
if (objId) { if (objId) {
const matrix = dict.getArray("Matrix"); let matrix = dict.getArray("Matrix");
if (!isNumberArray(matrix, 6)) {
matrix = null;
}
operatorList.addOp(fn, ["Shading", objId, matrix]); operatorList.addOp(fn, ["Shading", objId, matrix]);
} }
return undefined; return undefined;
@ -3266,7 +3269,7 @@ class PartialEvaluator {
const xObjStateManager = new StateManager(currentState); const xObjStateManager = new StateManager(currentState);
const matrix = xobj.dict.getArray("Matrix"); const matrix = xobj.dict.getArray("Matrix");
if (Array.isArray(matrix) && matrix.length === 6) { if (isNumberArray(matrix, 6)) {
xObjStateManager.transform(matrix); xObjStateManager.transform(matrix);
} }

View file

@ -16,14 +16,19 @@
import { import {
assert, assert,
FormatError, FormatError,
IDENTITY_MATRIX,
info, info,
unreachable, unreachable,
Util, Util,
warn, warn,
} from "../shared/util.js"; } from "../shared/util.js";
import {
isBooleanArray,
isNumberArray,
MissingDataException,
} from "./core_utils.js";
import { BaseStream } from "./base_stream.js"; import { BaseStream } from "./base_stream.js";
import { ColorSpace } from "./colorspace.js"; import { ColorSpace } from "./colorspace.js";
import { MissingDataException } from "./core_utils.js";
const ShadingType = { const ShadingType = {
FUNCTION_BASED: 1, FUNCTION_BASED: 1,
@ -106,8 +111,17 @@ class BaseShading {
class RadialAxialShading extends BaseShading { class RadialAxialShading extends BaseShading {
constructor(dict, xref, resources, pdfFunctionFactory, localColorSpaceCache) { constructor(dict, xref, resources, pdfFunctionFactory, localColorSpaceCache) {
super(); super();
this.coordsArr = dict.getArray("Coords");
this.shadingType = dict.get("ShadingType"); this.shadingType = dict.get("ShadingType");
let coordsLen = 0;
if (this.shadingType === ShadingType.AXIAL) {
coordsLen = 4;
} else if (this.shadingType === ShadingType.RADIAL) {
coordsLen = 6;
}
this.coordsArr = dict.getArray("Coords");
if (!isNumberArray(this.coordsArr, coordsLen)) {
throw new FormatError("RadialAxialShading: Invalid /Coords array.");
}
const cs = ColorSpace.parse({ const cs = ColorSpace.parse({
cs: dict.getRaw("CS") || dict.getRaw("ColorSpace"), cs: dict.getRaw("CS") || dict.getRaw("ColorSpace"),
xref, xref,
@ -116,25 +130,20 @@ class RadialAxialShading extends BaseShading {
localColorSpaceCache, localColorSpaceCache,
}); });
const bbox = dict.getArray("BBox"); const bbox = dict.getArray("BBox");
this.bbox = this.bbox = isNumberArray(bbox, 4) ? Util.normalizeRect(bbox) : null;
Array.isArray(bbox) && bbox.length === 4
? Util.normalizeRect(bbox)
: null;
let t0 = 0.0, let t0 = 0.0,
t1 = 1.0; t1 = 1.0;
if (dict.has("Domain")) {
const domainArr = dict.getArray("Domain"); const domainArr = dict.getArray("Domain");
t0 = domainArr[0]; if (isNumberArray(domainArr, 2)) {
t1 = domainArr[1]; [t0, t1] = domainArr;
} }
let extendStart = false, let extendStart = false,
extendEnd = false; extendEnd = false;
if (dict.has("Extend")) {
const extendArr = dict.getArray("Extend"); const extendArr = dict.getArray("Extend");
extendStart = extendArr[0]; if (isBooleanArray(extendArr, 2)) {
extendEnd = extendArr[1]; [extendStart, extendEnd] = extendArr;
} }
if ( if (
@ -271,8 +280,7 @@ class RadialAxialShading extends BaseShading {
} }
getIR() { getIR() {
const coordsArr = this.coordsArr; const { coordsArr, shadingType } = this;
const shadingType = this.shadingType;
let type, p0, p1, r0, r1; let type, p0, p1, r0, r1;
if (shadingType === ShadingType.AXIAL) { if (shadingType === ShadingType.AXIAL) {
p0 = [coordsArr[0], coordsArr[1]]; p0 = [coordsArr[0], coordsArr[1]];
@ -454,10 +462,7 @@ class MeshShading extends BaseShading {
const dict = stream.dict; const dict = stream.dict;
this.shadingType = dict.get("ShadingType"); this.shadingType = dict.get("ShadingType");
const bbox = dict.getArray("BBox"); const bbox = dict.getArray("BBox");
this.bbox = this.bbox = isNumberArray(bbox, 4) ? Util.normalizeRect(bbox) : null;
Array.isArray(bbox) && bbox.length === 4
? Util.normalizeRect(bbox)
: null;
const cs = ColorSpace.parse({ const cs = ColorSpace.parse({
cs: dict.getRaw("CS") || dict.getRaw("ColorSpace"), cs: dict.getRaw("CS") || dict.getRaw("ColorSpace"),
xref, xref,
@ -983,17 +988,32 @@ class DummyShading extends BaseShading {
} }
function getTilingPatternIR(operatorList, dict, color) { function getTilingPatternIR(operatorList, dict, color) {
const matrix = dict.getArray("Matrix"); let matrix = dict.getArray("Matrix");
const bbox = Util.normalizeRect(dict.getArray("BBox")); if (!isNumberArray(matrix, 6)) {
const xstep = dict.get("XStep"); matrix = IDENTITY_MATRIX;
const ystep = dict.get("YStep"); }
const paintType = dict.get("PaintType"); let bbox = dict.getArray("BBox");
const tilingType = dict.get("TilingType"); bbox = isNumberArray(bbox, 4) ? Util.normalizeRect(bbox) : null;
// Ensure that the pattern has a non-zero width and height, to prevent errors // Ensure that the pattern has a non-zero width and height, to prevent errors
// in `pattern_helper.js` (fixes issue8330.pdf). // in `pattern_helper.js` (fixes issue8330.pdf).
if (bbox[2] - bbox[0] === 0 || bbox[3] - bbox[1] === 0) { if (!bbox || bbox[2] - bbox[0] === 0 || bbox[3] - bbox[1] === 0) {
throw new FormatError(`Invalid getTilingPatternIR /BBox array: [${bbox}].`); throw new FormatError(`Invalid getTilingPatternIR /BBox array.`);
}
const xstep = dict.get("XStep");
if (typeof xstep !== "number") {
throw new FormatError(`Invalid getTilingPatternIR /XStep value.`);
}
const ystep = dict.get("YStep");
if (typeof ystep !== "number") {
throw new FormatError(`Invalid getTilingPatternIR /YStep value.`);
}
const paintType = dict.get("PaintType");
if (!Number.isInteger(paintType)) {
throw new FormatError(`Invalid getTilingPatternIR /PaintType value.`);
}
const tilingType = dict.get("TilingType");
if (!Number.isInteger(tilingType)) {
throw new FormatError(`Invalid getTilingPatternIR /TilingType value.`);
} }
return [ return [

View file

@ -2465,10 +2465,9 @@ class CanvasGraphics {
this.save(); this.save();
this.baseTransformStack.push(this.baseTransform); this.baseTransformStack.push(this.baseTransform);
if (Array.isArray(matrix) && matrix.length === 6) { if (matrix) {
this.transform(...matrix); this.transform(...matrix);
} }
this.baseTransform = getCurrentTransform(this.ctx); this.baseTransform = getCurrentTransform(this.ctx);
if (bbox) { if (bbox) {
@ -2652,7 +2651,7 @@ class CanvasGraphics {
this.ctx.setTransform(...this.baseTransform); this.ctx.setTransform(...this.baseTransform);
} }
if (Array.isArray(rect) && rect.length === 4) { if (rect) {
const width = rect[2] - rect[0]; const width = rect[2] - rect[0];
const height = rect[3] - rect[1]; const height = rect[3] - rect[1];

View file

@ -455,7 +455,7 @@ class TilingPattern {
constructor(IR, color, ctx, canvasGraphicsFactory, baseTransform) { constructor(IR, color, ctx, canvasGraphicsFactory, baseTransform) {
this.operatorList = IR[2]; this.operatorList = IR[2];
this.matrix = IR[3] || [1, 0, 0, 1, 0, 0]; this.matrix = IR[3];
this.bbox = IR[4]; this.bbox = IR[4];
this.xstep = IR[5]; this.xstep = IR[5];
this.ystep = IR[6]; this.ystep = IR[6];