validatePngContainer function

(int, int) validatePngContainer(
  1. List<int> bytes
)

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');
}