validatePngContainer function
Implementation
(int, int) validatePngContainer(List<int> bytes) {
Never invalid(String reason) => throw ArgumentError('Invalid PNG: $reason');
const signature = [137, 80, 78, 71, 13, 10, 26, 10];
if (bytes.length < 8 || bytes.any((byte) => byte < 0 || byte > 255)) {
invalid('expected PNG bytes');
}
for (var i = 0; i < signature.length; i++) {
if (bytes[i] != signature[i]) invalid('incorrect signature');
}
int u32(int offset) =>
bytes[offset] * 16777216 +
bytes[offset + 1] * 65536 +
bytes[offset + 2] * 256 +
bytes[offset + 3];
var offset = 8;
var width = 0;
var height = 0;
var colorType = -1;
var bitDepth = 0;
var palette = false;
var imageData = false;
var dataEnded = false;
var dataBytes = 0;
while (offset < bytes.length) {
if (bytes.length - offset < 12) invalid('truncated chunk');
final length = u32(offset);
if (length > 0x7fffffff || length > bytes.length - offset - 12) {
invalid('invalid chunk length');
}
final typeOffset = offset + 4;
final start = offset + 8;
final end = start + length;
for (var i = typeOffset; i < start; i++) {
final byte = bytes[i];
if (!((byte >= 65 && byte <= 90) || (byte >= 97 && byte <= 122))) {
invalid('invalid chunk name');
}
}
if ((bytes[typeOffset + 2] & 32) != 0) invalid('reserved chunk bit');
if (_crc32(bytes, typeOffset, end) != u32(end)) {
invalid('chunk checksum mismatch');
}
final type = String.fromCharCodes(bytes.sublist(typeOffset, start));
if (offset == 8 && type != 'IHDR') invalid('IHDR must be first');
if (imageData && type != 'IDAT') dataEnded = true;
switch (type) {
case 'IHDR':
if (offset != 8 || length != 13) invalid('invalid IHDR');
width = u32(start);
height = u32(start + 4);
if (width == 0 ||
height == 0 ||
width > 0x7fffffff ||
height > 0x7fffffff) {
invalid('invalid dimensions');
}
bitDepth = bytes[start + 8];
colorType = bytes[start + 9];
final allowedDepths = switch (colorType) {
0 => const [1, 2, 4, 8, 16],
2 || 4 || 6 => const [8, 16],
3 => const [1, 2, 4, 8],
_ => const <int>[],
};
if (!allowedDepths.contains(bitDepth) ||
bytes[start + 10] != 0 ||
bytes[start + 11] != 0 ||
bytes[start + 12] > 1) {
invalid('unsupported PNG header');
}
case 'PLTE':
if (palette ||
imageData ||
colorType == 0 ||
colorType == 4 ||
length == 0 ||
length % 3 != 0 ||
length > 768 ||
(colorType == 3 && length ~/ 3 > (1 << bitDepth))) {
invalid('invalid palette');
}
palette = true;
case 'IDAT':
if (dataEnded || (colorType == 3 && !palette)) {
invalid('invalid IDAT order');
}
imageData = true;
dataBytes += length;
case 'IEND':
if (length != 0 ||
!imageData ||
dataBytes == 0 ||
end + 4 != bytes.length) {
invalid('invalid image end');
}
return (width, height);
case 'acTL' || 'fcTL' || 'fdAT':
invalid('animated PNG is not supported');
default:
if ((bytes[typeOffset] & 32) == 0) invalid('unknown critical chunk');
}
offset = end + 4;
}
invalid('missing IEND');
}