Skip setFillColorN/setStrokeColorN operators without valid arguments (issue 21570)

The PDF document in question is corrupt, and note that even Adobe Reader (i.e. the PDF reference implementation) cannot render it correctly.

Also, guard all other set fill/stroke color operators similarly.
This commit is contained in:
Jonas Jenwald 2026-07-14 14:31:26 +02:00
parent accfa9a162
commit 2cf7d30763
5 changed files with 72 additions and 0 deletions

View File

@ -1991,40 +1991,64 @@ class PartialEvaluator {
return;
}
case OPS.setFillColor:
if (!isNumberArray(args, null)) {
continue;
}
cs = stateManager.state.fillColorSpace;
args = [cs.getRgbHex(args, 0)];
fn = OPS.setFillRGBColor;
break;
case OPS.setStrokeColor:
if (!isNumberArray(args, null)) {
continue;
}
cs = stateManager.state.strokeColorSpace;
args = [cs.getRgbHex(args, 0)];
fn = OPS.setStrokeRGBColor;
break;
case OPS.setFillGray:
if (!isNumberArray(args, null)) {
continue;
}
stateManager.state.fillColorSpace = ColorSpaceUtils.gray;
args = [ColorSpaceUtils.gray.getRgbHex(args, 0)];
fn = OPS.setFillRGBColor;
break;
case OPS.setStrokeGray:
if (!isNumberArray(args, null)) {
continue;
}
stateManager.state.strokeColorSpace = ColorSpaceUtils.gray;
args = [ColorSpaceUtils.gray.getRgbHex(args, 0)];
fn = OPS.setStrokeRGBColor;
break;
case OPS.setFillCMYKColor:
if (!isNumberArray(args, null)) {
continue;
}
stateManager.state.fillColorSpace = ColorSpaceUtils.cmyk;
args = [ColorSpaceUtils.cmyk.getRgbHex(args, 0)];
fn = OPS.setFillRGBColor;
break;
case OPS.setStrokeCMYKColor:
if (!isNumberArray(args, null)) {
continue;
}
stateManager.state.strokeColorSpace = ColorSpaceUtils.cmyk;
args = [ColorSpaceUtils.cmyk.getRgbHex(args, 0)];
fn = OPS.setStrokeRGBColor;
break;
case OPS.setFillRGBColor:
if (!isNumberArray(args, null)) {
continue;
}
stateManager.state.fillColorSpace = ColorSpaceUtils.rgb;
args = [ColorSpaceUtils.rgb.getRgbHex(args, 0)];
break;
case OPS.setStrokeRGBColor:
if (!isNumberArray(args, null)) {
continue;
}
stateManager.state.strokeColorSpace = ColorSpaceUtils.rgb;
args = [ColorSpaceUtils.rgb.getRgbHex(args, 0)];
break;
@ -2041,6 +2065,9 @@ class PartialEvaluator {
break;
}
if (cs.name === "Pattern") {
if (!Array.isArray(args)) {
continue;
}
next(
self.handleColorN(
operatorList,
@ -2058,6 +2085,9 @@ class PartialEvaluator {
);
return;
}
if (!isNumberArray(args, null)) {
continue;
}
args = [cs.getRgbHex(args, 0)];
fn = OPS.setFillRGBColor;
break;
@ -2074,6 +2104,9 @@ class PartialEvaluator {
break;
}
if (cs.name === "Pattern") {
if (!Array.isArray(args)) {
continue;
}
next(
self.handleColorN(
operatorList,
@ -2091,6 +2124,9 @@ class PartialEvaluator {
);
return;
}
if (!isNumberArray(args, null)) {
continue;
}
args = [cs.getRgbHex(args, 0)];
fn = OPS.setStrokeRGBColor;
break;

View File

@ -103,6 +103,7 @@
!issue9713.pdf
!xfa_filled_imm1344e.pdf
!issue8424.pdf
!issue21570.pdf
!issue8480.pdf
!bug1650302_reduced.pdf
!issue18816.pdf

BIN
test/pdfs/issue21570.pdf Normal file

Binary file not shown.

View File

@ -13059,6 +13059,13 @@
}
}
},
{
"id": "issue21570",
"file": "pdfs/issue21570.pdf",
"md5": "3065e65224901203e708e3dacad2d99a",
"rounds": 1,
"type": "eq"
},
{
"id": "issue2856",
"file": "pdfs/issue2856.pdf",

View File

@ -17,6 +17,7 @@ import { createIdFactory, XRefMock } from "./test_utils.js";
import { Dict, Name } from "../../src/core/primitives.js";
import { FormatError, OPS } from "../../src/shared/util.js";
import { Stream, StringStream } from "../../src/core/stream.js";
import { GlobalColorSpaceCache } from "../../src/core/image_utils.js";
import { OperatorList } from "../../src/core/operator_list.js";
import { PartialEvaluator } from "../../src/core/evaluator.js";
import { WorkerTask } from "../../src/core/worker.js";
@ -57,6 +58,7 @@ describe("evaluator", function () {
handler: new HandlerMock(),
pageIndex: 0,
idFactory: createIdFactory(/* pageIndex = */ 0),
globalColorSpaceCache: new GlobalColorSpaceCache(),
});
});
@ -358,6 +360,32 @@ describe("evaluator", function () {
expect(result.fnArray).toEqual([]);
});
it("should skip empty set fill/stroke color operators", async function () {
const colorOps = [
"SC",
"SCN",
"sc",
"scn",
"G",
"g",
"RG",
"rg",
"K",
"k",
];
for (const op of colorOps) {
const stream = new StringStream(`/DeviceRGB CS /DeviceRGB cs ${op}`);
const result = await runOperatorListCheck(
partialEvaluator,
stream,
new ResourcesMock()
);
expect(result.argsArray).toEqual([]);
expect(result.fnArray).toEqual([]);
}
});
it("should handle invalid dash stuff", async function () {
const stream = new StringStream("[ none ] 0 d");
const result = await runOperatorListCheck(