Avoid infinite loop when getting annotation field name

- aims to fix issue #12963;
 - use a Set to track already visited objects;
 - remove the loop limit in getInheritableProperty and use a RefSet too.
This commit is contained in:
Calixte Denizet 2021-02-06 12:23:35 +01:00
parent f892c00275
commit 0fc8267576
6 changed files with 39 additions and 36 deletions

View File

@ -39,7 +39,15 @@ import {
createDefaultAppearance,
parseDefaultAppearance,
} from "./default_appearance.js";
import { Dict, isDict, isName, isRef, isStream, Name } from "./primitives.js";
import {
Dict,
isDict,
isName,
isRef,
isStream,
Name,
RefSet,
} from "./primitives.js";
import { ColorSpace } from "./colorspace.js";
import { OperatorList } from "./operator_list.js";
import { StringStream } from "./stream.js";
@ -1066,14 +1074,27 @@ class WidgetAnnotation extends Annotation {
}
let loopDict = dict;
const visited = new RefSet();
if (dict.objId) {
visited.put(dict.objId);
}
while (loopDict.has("Parent")) {
loopDict = loopDict.get("Parent");
if (!isDict(loopDict)) {
if (
!(loopDict instanceof Dict) ||
(loopDict.objId && visited.has(loopDict.objId))
) {
// Even though it is not allowed according to the PDF specification,
// bad PDF generators may provide a `Parent` entry that is not a
// dictionary, but `null` for example (issue 8143).
//
// If parent has been already visited, it means that we're
// in an infinite loop.
break;
}
if (loopDict.objId) {
visited.put(loopDict.objId);
}
if (loopDict.has("T")) {
fieldName.unshift(stringToPDFString(loopDict.get("T")));

View File

@ -19,7 +19,6 @@ import {
bytesToString,
objectSize,
stringToPDFString,
warn,
} from "../shared/util.js";
import { Dict, isName, isRef, isStream, RefSet } from "./primitives.js";
@ -72,8 +71,7 @@ class XRefParseException extends BaseException {}
*
* If the key is not found in the tree, `undefined` is returned. Otherwise,
* the value for the key is returned or, if `stopWhenFound` is `false`, a list
* of values is returned. To avoid infinite loops, the traversal is stopped when
* the loop limit is reached.
* of values is returned.
*
* @param {Dict} dict - Dictionary from where to start the traversal.
* @param {string} key - The key of the property to find the value for.
@ -90,11 +88,13 @@ function getInheritableProperty({
getArray = false,
stopWhenFound = true,
}) {
const LOOP_LIMIT = 100;
let loopCount = 0;
let values;
const visited = new RefSet();
while (dict) {
while (dict instanceof Dict && !(dict.objId && visited.has(dict.objId))) {
if (dict.objId) {
visited.put(dict.objId);
}
const value = getArray ? dict.getArray(key) : dict.get(key);
if (value !== undefined) {
if (stopWhenFound) {
@ -105,10 +105,6 @@ function getInheritableProperty({
}
values.push(value);
}
if (++loopCount > LOOP_LIMIT) {
warn(`getInheritableProperty: maximum loop count exceeded for "${key}"`);
break;
}
dict = dict.get("Parent");
}
return values;

View File

@ -76,6 +76,7 @@
!issue8798r.pdf
!issue8823.pdf
!issue9084.pdf
!issue12963.pdf
!issue9105_reduced.pdf
!issue9252.pdf
!issue9262_reduced.pdf

BIN
test/pdfs/issue12963.pdf Normal file

Binary file not shown.

View File

@ -2445,6 +2445,15 @@
"lastPage": 1,
"type": "eq"
},
{ "id": "issue12963",
"file": "pdfs/issue12963.pdf",
"md5": "2590047a2ef0113e698d888c29918008",
"rounds": 1,
"firstPage": 1,
"lastPage": 1,
"type": "eq",
"annotations": true
},
{ "id": "multiple-filters-length-zero",
"file": "pdfs/multiple-filters-length-zero.pdf",
"md5": "c273c3a6fb79cbf3034fe1b62b204728",

View File

@ -123,30 +123,6 @@ describe("core_utils", function () {
["qux2", "quux"],
]);
});
it("stops searching when the loop limit is reached", function () {
const dict = new Dict();
let currentDict = dict;
let parentDict = null;
// Exceed the loop limit of 100.
for (let i = 0; i < 150; i++) {
parentDict = new Dict();
currentDict.set("Parent", parentDict);
currentDict = parentDict;
}
parentDict.set("foo", "bar"); // Never found because of loop limit.
expect(getInheritableProperty({ dict, key: "foo" })).toEqual(undefined);
dict.set("foo", "baz");
expect(
getInheritableProperty({
dict,
key: "foo",
getArray: false,
stopWhenFound: false,
})
).toEqual(["baz"]);
});
});
describe("toRomanNumerals", function () {