From 2cb113b545bc940d14b1554e70c38fba14e2bc3c Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Thu, 15 Jun 2023 20:48:54 +0200 Subject: [PATCH] Improve handling of /Filter-entries in `writeStream` Fix handling of /Filter-entries, since the current implementation could potentially corrupt the data if there's multiple filters present. Please note that filters are applied *sequentially* during decoding, starting from the first one in the Array, hence the first Array-entry needs to be /FlateDecode in order for things to actually work correctly. To prevent a future bug, if we want to save more "complex" data such as images, also ensure that we include any existing /DecodeParms-entries when updating the /Filter-entry. --- src/core/writer.js | 56 ++++++++++++++++++++++++------------------- test/unit/api_spec.js | 2 +- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/src/core/writer.js b/src/core/writer.js index 6c30acf64..7c834c3c1 100644 --- a/src/core/writer.js +++ b/src/core/writer.js @@ -14,7 +14,7 @@ */ import { bytesToString, info, stringToBytes, warn } from "../shared/util.js"; -import { Dict, Name, Ref } from "./primitives.js"; +import { Dict, isName, Name, Ref } from "./primitives.js"; import { escapePDFName, escapeString, @@ -49,28 +49,29 @@ async function writeStream(stream, buffer, transform) { if (transform !== null) { string = transform.encryptString(string); } + const { dict } = stream; // eslint-disable-next-line no-undef if (typeof CompressionStream === "undefined") { - stream.dict.set("Length", string.length); - await writeDict(stream.dict, buffer, transform); + dict.set("Length", string.length); + await writeDict(dict, buffer, transform); buffer.push(" stream\n", string, "\nendstream"); return; } - const filter = await stream.dict.getAsync("Filter"); - const flateDecode = Name.get("FlateDecode"); + const filter = await dict.getAsync("Filter"); + const params = await dict.getAsync("DecodeParms"); - // If the string is too small there is no real benefit - // in compressing it. + const filterZero = Array.isArray(filter) + ? await dict.xref.fetchIfRefAsync(filter[0]) + : filter; + const isFilterZeroFlateDecode = isName(filterZero, "FlateDecode"); + + // If the string is too small there is no real benefit in compressing it. // The number 256 is arbitrary, but it should be reasonable. const MIN_LENGTH_FOR_COMPRESSING = 256; - if ( - string.length >= MIN_LENGTH_FOR_COMPRESSING || - (Array.isArray(filter) && filter.includes(flateDecode)) || - (filter instanceof Name && filter.name === flateDecode.name) - ) { + if (string.length >= MIN_LENGTH_FOR_COMPRESSING || isFilterZeroFlateDecode) { try { const byteArray = stringToBytes(string); // eslint-disable-next-line no-undef @@ -83,25 +84,32 @@ async function writeStream(stream, buffer, transform) { const buf = await new Response(cs.readable).arrayBuffer(); string = bytesToString(new Uint8Array(buf)); - if (Array.isArray(filter)) { - if (!filter.includes(flateDecode)) { - filter.push(flateDecode); + let newFilter, newParams; + if (!filter) { + newFilter = Name.get("FlateDecode"); + } else if (!isFilterZeroFlateDecode) { + newFilter = Array.isArray(filter) + ? [Name.get("FlateDecode"), ...filter] + : [Name.get("FlateDecode"), filter]; + if (params) { + newParams = Array.isArray(params) + ? [null, ...params] + : [null, params]; } - } else if (!filter) { - stream.dict.set("Filter", flateDecode); - } else if ( - !(filter instanceof Name) || - filter.name !== flateDecode.name - ) { - stream.dict.set("Filter", [filter, flateDecode]); + } + if (newFilter) { + dict.set("Filter", newFilter); + } + if (newParams) { + dict.set("DecodeParms", newParams); } } catch (ex) { info(`writeStream - cannot compress data: "${ex}".`); } } - stream.dict.set("Length", string.length); - await writeDict(stream.dict, buffer, transform); + dict.set("Length", string.length); + await writeDict(dict, buffer, transform); buffer.push(" stream\n", string, "\nendstream"); } diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index 188fbbee4..d323a8d3a 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -1979,7 +1979,7 @@ describe("api", function () { await loadingTask.destroy(); }); - it("write a a new annotation, save the pdf and check that the prev entry in xref stream is correct", async function () { + it("write a new annotation, save the pdf and check that the prev entry in xref stream is correct", async function () { if (isNodeJS) { pending("Linked test-cases are not supported in Node.js."); }