diff --git a/src/index.test.js b/src/index.test.js index 070d920..47f81c9 100644 --- a/src/index.test.js +++ b/src/index.test.js @@ -120,7 +120,7 @@ test('returns false when acTL is not a chunk type', (t) => { // Chunk length: 4 0x00, 0x00, 0x00, 0x04, // Chunk type: any - 0x66, 0x66, 0x66, 0x66, + 0x00, 0x00, 0x00, 0x01, // Chunk data: acTL 0x61, 0x63, 0x54, 0x4c, // Chunk CRC @@ -151,45 +151,15 @@ test('returns false when next chunk size is too small', (t) => { // Chunk length: 4 0x00, 0x00, 0x00, 0x04, // Chunk type: any - 0x66, 0x66, 0x66, 0x66, + 0x00, 0x00, 0x00, 0x01, // Chunk CRC 0x00, 0x00, 0x00, 0x00, // Chunk length: 4 0x00, 0x00, 0x00, 0x04, // Chunk type: any - 0x66, 0x66, 0x66, 0x66, + 0x00, 0x00, 0x00, 0x02, // Chunk CRC omitted ]), ), ) }) - -test('returns false when chunk type is invalid', (t) => { - t.false( - isApng( - new Uint8Array([ - // PNG header - 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, - // Chunk length: 0 - 0x00, 0x00, 0x00, 0x00, - // Chunk type: invalid bytes - 0x00, 0x00, 0x00, 0x01, - ]), - ), - ) -}) - -test('returns false when unknown critical chunk type', (t) => { - t.false( - isApng( - new Uint8Array([ - // PNG header - 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, - // Chunk length: 0 - 0x00, 0x00, 0x00, 0x00, - // Chunk type: unknown critical - 0x55, 0x55, 0x55, 0x55, - ]), - ), - ) -}) diff --git a/src/index.ts b/src/index.ts index d0bf997..bf59464 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,33 +1,11 @@ -function bytesToInt(bytes: Uint8Array): number { - return bytes.reduce((value, byte) => (value << 8) + byte, 0) -} - -function chunkTypeToInt(bytes: Uint8Array | number[]): number { - if (bytes.length !== headerSizes.TYPE) { - throw new Error(`Invalid chunk type size ${bytes.length}`) - } - - let value = 0 - for (let i = 0; i < bytes.length; i++) { - const byte = bytes[i] - - if (!(byte >= 0x41 && byte <= 0x5a) && !(byte >= 0x61 && byte <= 0x7a)) { - const bytesText = Array.from(bytes) - .map((byte) => `0x${byte.toString(16)}`) - .join() - throw new Error(`Invalid chunk type ${bytesText}`) - } - - value = (value << 8) + bytes[i] - } - - return value +function convertToInt(bytes: Uint8Array) { + return bytes.reduce((value, byte) => (value << 8) + byte) } function isEqual( first: Uint8Array | number[], second: Uint8Array | number[], - length, + length = 4, ) { while (length > 0) { length-- @@ -54,25 +32,10 @@ const headerSizes = { const chunkTypes = { signature: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], - /** `IHDR` chunk type */ - imageHeader: chunkTypeToInt([0x49, 0x48, 0x44, 0x52]), - /** `PLTE` chunk type */ - palette: chunkTypeToInt([0x50, 0x4c, 0x54, 0x45]), - /** `acTL` chunk type */ - animationControl: chunkTypeToInt([0x61, 0x63, 0x54, 0x4c]), - /** `IDAT` chunk type */ - imageData: chunkTypeToInt([0x49, 0x44, 0x41, 0x54]), - /** `IEND` chunk type */ - imageEnd: chunkTypeToInt([0x49, 0x45, 0x4e, 0x44]), + animationControl: [0x61, 0x63, 0x54, 0x4c], // 'acTL' + imageData: [0x49, 0x44, 0x41, 0x54], // 'IDAT' } -const knownCriticalChunkTypes = new Set([ - chunkTypes.imageHeader, - chunkTypes.palette, - chunkTypes.imageData, - chunkTypes.imageEnd, -]) - export default function isApng(buffer: Uint8Array): boolean { const minChunkSize = headerSizes.LENGTH + headerSizes.TYPE + headerSizes.CRC @@ -97,29 +60,21 @@ export default function isApng(buffer: Uint8Array): boolean { */ while (buffer.length >= minChunkSize) { - const chunkType = chunkTypeToInt( - buffer.subarray( - headerSizes.LENGTH, - headerSizes.LENGTH + headerSizes.TYPE, - ), + const chunkType = buffer.subarray( + headerSizes.LENGTH, + headerSizes.LENGTH + headerSizes.TYPE, ) - // Sixth bit of the first byte of the chunk type is critical property - // (0 - critical, 1 - not) - const isCriticalChunk = !(chunkType & 0x20000000) - if (isCriticalChunk && !knownCriticalChunkTypes.has(chunkType)) { - return false + if (isEqual(chunkType, chunkTypes.animationControl, headerSizes.TYPE)) { + return true } - switch (chunkType) { - case chunkTypes.animationControl: - return true - case chunkTypes.imageData: - return false + if (isEqual(chunkType, chunkTypes.imageData, headerSizes.TYPE)) { + return false } const nextChunkPosition = - minChunkSize + bytesToInt(buffer.subarray(0, headerSizes.LENGTH)) + minChunkSize + convertToInt(buffer.subarray(0, headerSizes.LENGTH)) buffer = buffer.subarray(nextChunkPosition) }