Skip to content

Commit

Permalink
Revert "fix: Validate chunk types"
Browse files Browse the repository at this point in the history
This reverts commit 60087bc.
  • Loading branch information
vHeemstra committed Aug 12, 2024
1 parent 60087bc commit 9a7232a
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 91 deletions.
36 changes: 3 additions & 33 deletions src/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
]),
),
)
})
71 changes: 13 additions & 58 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -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--
Expand All @@ -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<number>([
chunkTypes.imageHeader,
chunkTypes.palette,
chunkTypes.imageData,
chunkTypes.imageEnd,
])

export default function isApng(buffer: Uint8Array): boolean {
const minChunkSize = headerSizes.LENGTH + headerSizes.TYPE + headerSizes.CRC

Expand All @@ -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)
}
Expand Down

0 comments on commit 9a7232a

Please sign in to comment.