From 08eb0566f712525363402ee4a272f1b5fc1d0c63 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 28 Apr 2024 11:50:57 +0200 Subject: [PATCH] Validate additional font-dictionary properties --- src/core/core_utils.js | 16 ++++++++++ src/core/evaluator.js | 65 ++++++++++++++++++++++++++++++--------- src/core/font_renderer.js | 3 +- src/core/function.js | 14 +++------ 4 files changed, 72 insertions(+), 26 deletions(-) diff --git a/src/core/core_utils.js b/src/core/core_utils.js index c3de1210fb883..c6ddb8f84ab1b 100644 --- a/src/core/core_utils.js +++ b/src/core/core_utils.js @@ -218,6 +218,21 @@ function isWhiteSpace(ch) { return ch === 0x20 || ch === 0x09 || ch === 0x0d || ch === 0x0a; } +/** + * Checks if something is an Array containing only numbers, + * and (optionally) checks its length. + * @param {any} arr + * @param {number | null} len + * @returns {boolean} + */ +function isNumberArray(arr, len) { + return ( + Array.isArray(arr) && + (len === null || arr.length === len) && + arr.every(x => typeof x === "number") + ); +} + /** * AcroForm field names use an array like notation to refer to * repeated XFA elements e.g. foo.bar[nnn]. @@ -637,6 +652,7 @@ export { getRotationMatrix, getSizeInBytes, isAscii, + isNumberArray, isWhiteSpace, log2, MissingDataException, diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 17dd3bbc15b40..7dd0dcec3a1a6 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -73,6 +73,7 @@ import { getGlyphsUnicode } from "./glyphlist.js"; import { getMetrics } from "./metrics.js"; import { getUnicodeForGlyph } from "./unicode.js"; import { ImageResizer } from "./image_resizer.js"; +import { isNumberArray } from "./core_utils.js"; import { MurmurHash3_64 } from "../shared/murmurhash3.js"; import { OperatorList } from "./operator_list.js"; import { PDFImage } from "./image.js"; @@ -4077,8 +4078,14 @@ class PartialEvaluator { composite = true; } - const firstChar = dict.get("FirstChar") || 0, - lastChar = dict.get("LastChar") || (composite ? 0xffff : 0xff); + let firstChar = dict.get("FirstChar"); + if (!Number.isInteger(firstChar)) { + firstChar = 0; + } + let lastChar = dict.get("LastChar"); + if (!Number.isInteger(lastChar)) { + lastChar = composite ? 0xffff : 0xff; + } const descriptor = dict.get("FontDescriptor"); const toUnicode = dict.get("ToUnicode") || baseDict.get("ToUnicode"); @@ -4206,11 +4213,15 @@ class PartialEvaluator { if (!descriptor) { if (isType3Font) { + let bbox = dict.getArray("FontBBox"); + if (!isNumberArray(bbox, 4)) { + bbox = [0, 0, 0, 0]; + } // FontDescriptor is only required for Type3 fonts when the document // is a tagged pdf. Create a barbebones one to get by. descriptor = new Dict(null); descriptor.set("FontName", Name.get(type)); - descriptor.set("FontBBox", dict.getArray("FontBBox") || [0, 0, 0, 0]); + descriptor.set("FontBBox", bbox); } else { // Before PDF 1.5 if the font was one of the base 14 fonts, having a // FontDescriptor was not required. @@ -4392,13 +4403,37 @@ class PartialEvaluator { } let fontMatrix = dict.getArray("FontMatrix"); - if ( - !Array.isArray(fontMatrix) || - fontMatrix.length !== 6 || - fontMatrix.some(x => typeof x !== "number") - ) { + if (!isNumberArray(fontMatrix, 6)) { fontMatrix = FONT_IDENTITY_MATRIX; } + let bbox = descriptor.getArray("FontBBox") || dict.getArray("FontBBox"); + if (!isNumberArray(bbox, 4)) { + bbox = undefined; + } + let ascent = descriptor.get("Ascent"); + if (typeof ascent !== "number") { + ascent = undefined; + } + let descent = descriptor.get("Descent"); + if (typeof descent !== "number") { + descent = undefined; + } + let xHeight = descriptor.get("XHeight"); + if (typeof xHeight !== "number") { + xHeight = 0; + } + let capHeight = descriptor.get("CapHeight"); + if (typeof capHeight !== "number") { + capHeight = 0; + } + let flags = descriptor.get("Flags"); + if (!Number.isInteger(flags)) { + flags = 0; + } + let italicAngle = descriptor.get("ItalicAngle"); + if (typeof italicAngle !== "number") { + italicAngle = 0; + } const properties = { type, @@ -4416,13 +4451,13 @@ class PartialEvaluator { firstChar, lastChar, toUnicode, - bbox: descriptor.getArray("FontBBox") || dict.getArray("FontBBox"), - ascent: descriptor.get("Ascent"), - descent: descriptor.get("Descent"), - xHeight: descriptor.get("XHeight") || 0, - capHeight: descriptor.get("CapHeight") || 0, - flags: descriptor.get("Flags"), - italicAngle: descriptor.get("ItalicAngle") || 0, + bbox, + ascent, + descent, + xHeight, + capHeight, + flags, + italicAngle, isType3Font, cssFontInfo, scaleFactors: glyphScaleFactors, diff --git a/src/core/font_renderer.js b/src/core/font_renderer.js index 3c7ec113dc15d..d5d1b1f2acfc0 100644 --- a/src/core/font_renderer.js +++ b/src/core/font_renderer.js @@ -23,6 +23,7 @@ import { } from "../shared/util.js"; import { CFFParser } from "./cff_parser.js"; import { getGlyphsUnicode } from "./glyphlist.js"; +import { isNumberArray } from "./core_utils.js"; import { StandardEncoding } from "./encodings.js"; import { Stream } from "./stream.js"; @@ -750,7 +751,7 @@ class Commands { add(cmd, args) { if (args) { - if (args.some(arg => typeof arg !== "number")) { + if (!isNumberArray(args, null)) { warn( `Commands.add - "${cmd}" has at least one non-number arg: "${args}".` ); diff --git a/src/core/function.js b/src/core/function.js index 1e1d00336d786..618eeee233572 100644 --- a/src/core/function.js +++ b/src/core/function.js @@ -23,6 +23,7 @@ import { } from "../shared/util.js"; import { PostScriptLexer, PostScriptParser } from "./ps_parser.js"; import { BaseStream } from "./base_stream.js"; +import { isNumberArray } from "./core_utils.js"; import { LocalFunctionCache } from "./image_utils.js"; class PDFFunctionFactory { @@ -117,16 +118,9 @@ function toNumberArray(arr) { if (!Array.isArray(arr)) { return null; } - const length = arr.length; - for (let i = 0; i < length; i++) { - if (typeof arr[i] !== "number") { - // Non-number is found -- convert all items to numbers. - const result = new Array(length); - for (let j = 0; j < length; j++) { - result[j] = +arr[j]; - } - return result; - } + if (!isNumberArray(arr, null)) { + // Non-number is found -- convert all items to numbers. + return arr.map(x => +x); } return arr; }