Skip to content

Commit 80391ef

Browse files
authored
fix(png): incorrect expected behaviour when decoding (#18)
2 parents fe5c9f2 + 953ae68 commit 80391ef

3 files changed

Lines changed: 71 additions & 46 deletions

File tree

png/decode/mod.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -146,25 +146,31 @@ export async function decodePNG(
146146
let chunkPLTE: Uint8Array | Uint8ClampedArray | undefined;
147147
let chunktRNS: Uint8Array | Uint8ClampedArray | undefined;
148148
let lastChunkWasIDAT = false;
149+
let lastType: Uint8Array | Uint8ClampedArray | undefined;
149150
for (let i = chunkIHDR.o; i < input.length;) {
150151
const { o, type, data } = getChunk(input, view, i);
151152
i = o;
152153
if ([73, 68, 65, 84].every((x, i) => x === type[i])) {
154+
if (!lastChunkWasIDAT && chunksIDAT.length) {
155+
throw new TypeError(
156+
`A non-IDAT chunk (${
157+
new TextDecoder().decode(lastType)
158+
}) was found between IDAT chunks`,
159+
);
160+
}
153161
chunksIDAT.push(data);
154162
lastChunkWasIDAT = true;
155-
} else if (lastChunkWasIDAT) {
156-
if ([73, 69, 78, 68].every((x, i) => x === type[i])) break;
157-
throw new TypeError(
158-
"An IDAT or IEND chunk was expected. Found: " +
159-
new TextDecoder().decode(type),
160-
);
161-
} else if ([80, 76, 84, 69].every((x, i) => x === type[i])) {
162-
if (chunkPLTE == undefined) chunkPLTE = data.slice();
163-
else throw new TypeError("A PLTE chunk was already received");
164-
} else if ([116, 82, 78, 83].every((x, i) => x === type[i])) {
165-
if (chunktRNS == undefined) chunktRNS = data.slice();
166-
else throw new TypeError("A tRNS chunk was already received");
163+
} else {
164+
lastChunkWasIDAT = false;
165+
if ([80, 76, 84, 69].every((x, i) => x === type[i])) {
166+
if (chunkPLTE == undefined) chunkPLTE = data.slice();
167+
else throw new TypeError("A PLTE chunk was already received");
168+
} else if ([116, 82, 78, 83].every((x, i) => x === type[i])) {
169+
if (chunktRNS == undefined) chunktRNS = data.slice();
170+
else throw new TypeError("A tRNS chunk was already received");
171+
} else if ([73, 69, 78, 68].every((x, i) => x === type[i])) break;
167172
}
173+
lastType = type;
168174
}
169175
switch (colorType) {
170176
case 3:

png/decode/mod_test.ts

Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -276,41 +276,60 @@ Deno.test("decodePNG() rejects due to unsupported bitDepth", async () => {
276276
);
277277
});
278278

279-
Deno.test("decodePNG() rejects due to expecting an IDAT or IEND chunk", async () => {
280-
const encoded = await encodePNG(new Uint8Array(4), {
281-
width: 1,
282-
height: 1,
283-
compression: 0,
284-
filter: 0,
285-
interlace: 0,
286-
});
287-
const view = new DataView(
288-
encoded.buffer,
289-
encoded.byteOffset,
290-
encoded.byteLength,
291-
);
292-
const [offset, chunk] = function (): [number, Chunk] {
293-
for (let o = 8; o < encoded.length;) {
294-
const chunk = getChunk(encoded, view, o);
295-
if ([73, 69, 78, 68].every((x, i) => x === chunk.type[i])) {
296-
return [o, chunk];
279+
Deno.test(
280+
"decodePNG() rejects due to a non-IDAT chunk existing in between IDAT chunks",
281+
async () => {
282+
let encoded = await encodePNG(new Uint8Array(4), {
283+
width: 1,
284+
height: 1,
285+
compression: 0,
286+
filter: 0,
287+
interlace: 0,
288+
});
289+
const view = new DataView(
290+
encoded.buffer,
291+
encoded.byteOffset,
292+
encoded.byteLength,
293+
);
294+
const offset = function (): number {
295+
for (let o = 8; o < encoded.length;) {
296+
const chunk = getChunk(encoded, view, o);
297+
if ([73, 69, 78, 68].every((x, i) => x === chunk.type[i])) {
298+
return o;
299+
}
300+
o += chunk.length + 12;
297301
}
298-
o += chunk.length + 12;
299-
}
300-
throw new Error("INVALID");
301-
}();
302-
++chunk.type[0];
303-
view.setUint32(
304-
offset + 8 + chunk.length,
305-
calcCRC(encoded.subarray(offset + 4, offset + 8 + chunk.length)),
306-
);
302+
throw new Error("INVALID");
303+
}();
304+
305+
const chunkJDAT = new Uint8Array(12);
306+
chunkJDAT.set([74, 68, 65, 84], 4);
307+
let crc = calcCRC(chunkJDAT.subarray(4, -4));
308+
chunkJDAT[8] = crc >> 24 & 0xFF;
309+
chunkJDAT[9] = crc >> 16 & 0xFF;
310+
chunkJDAT[10] = crc >> 8 & 0xFF;
311+
chunkJDAT[11] = crc & 0xFF;
312+
const chunkIDAT = new Uint8Array(12);
313+
chunkIDAT.set([73, 68, 65, 84], 4);
314+
crc = calcCRC(chunkIDAT.subarray(4, -4));
315+
chunkIDAT[8] = crc >> 24 & 0xFF;
316+
chunkIDAT[9] = crc >> 16 & 0xFF;
317+
chunkIDAT[10] = crc >> 8 & 0xFF;
318+
chunkIDAT[11] = crc & 0xFF;
319+
encoded = concat([
320+
encoded.subarray(0, offset),
321+
chunkJDAT,
322+
chunkIDAT,
323+
encoded.subarray(offset),
324+
]);
307325

308-
await assertRejects(
309-
() => decodePNG(encoded),
310-
TypeError,
311-
"An IDAT or IEND chunk was expected. Found: JEND",
312-
);
313-
});
326+
await assertRejects(
327+
() => decodePNG(encoded),
328+
TypeError,
329+
"A non-IDAT chunk (JDAT) was found between IDAT chunks",
330+
);
331+
},
332+
);
314333

315334
Deno.test("decodePNG() rejects due to receiving too many PLTE chunks", async () => {
316335
let encoded = await encodePNG(Uint8Array.from([0, 1, 2, 3]), {

png/deno.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@img/png",
3-
"version": "0.1.1",
3+
"version": "0.1.2",
44
"exports": {
55
".": "./mod.ts",
66
"./encode": "./encode/mod.ts",

0 commit comments

Comments
 (0)