From 0b3a8c6c49939a9ff1ced1ab33a50e106230c0f4 Mon Sep 17 00:00:00 2001 From: kemal Date: Wed, 23 Jul 2025 11:50:08 +0300 Subject: [PATCH] Tolerate chunks in between APNG chunks --- YYImage/YYImageCoder.m | 198 +++++++++++++++-------------------------- 1 file changed, 72 insertions(+), 126 deletions(-) diff --git a/YYImage/YYImageCoder.m b/YYImage/YYImageCoder.m index f4597cb..440dfb0 100644 --- a/YYImage/YYImageCoder.m +++ b/YYImage/YYImageCoder.m @@ -318,82 +318,6 @@ static double yy_png_delay_to_seconds(uint16_t num, uint16_t den) { } } -static bool yy_png_validate_animation_chunk_order(yy_png_chunk_info *chunks, /* input */ - uint32_t chunk_num, /* input */ - uint32_t *first_idat_index, /* output */ - bool *first_frame_is_cover /* output */) { - /* - PNG at least contains 3 chunks: IHDR, IDAT, IEND. - `IHDR` must appear first. - `IDAT` must appear consecutively. - `IEND` must appear end. - - APNG must contains one `acTL` and at least one 'fcTL' and `fdAT`. - `fdAT` must appear consecutively. - `fcTL` must appear before `IDAT` or `fdAT`. - */ - if (chunk_num <= 2) return false; - if (chunks->fourcc != YY_FOUR_CC('I', 'H', 'D', 'R')) return false; - if ((chunks + chunk_num - 1)->fourcc != YY_FOUR_CC('I', 'E', 'N', 'D')) return false; - - uint32_t prev_fourcc = 0; - uint32_t IHDR_num = 0; - uint32_t IDAT_num = 0; - uint32_t acTL_num = 0; - uint32_t fcTL_num = 0; - uint32_t first_IDAT = 0; - bool first_frame_cover = false; - for (uint32_t i = 0; i < chunk_num; i++) { - yy_png_chunk_info *chunk = chunks + i; - switch (chunk->fourcc) { - case YY_FOUR_CC('I', 'H', 'D', 'R'): { // png header - if (i != 0) return false; - if (IHDR_num > 0) return false; - IHDR_num++; - } break; - case YY_FOUR_CC('I', 'D', 'A', 'T'): { // png data - if (prev_fourcc != YY_FOUR_CC('I', 'D', 'A', 'T')) { - if (IDAT_num == 0) - first_IDAT = i; - else - return false; - } - IDAT_num++; - } break; - case YY_FOUR_CC('a', 'c', 'T', 'L'): { // apng control - if (acTL_num > 0) return false; - acTL_num++; - } break; - case YY_FOUR_CC('f', 'c', 'T', 'L'): { // apng frame control - if (i + 1 == chunk_num) return false; - if ((chunk + 1)->fourcc != YY_FOUR_CC('f', 'd', 'A', 'T') && - (chunk + 1)->fourcc != YY_FOUR_CC('I', 'D', 'A', 'T')) { - return false; - } - if (fcTL_num == 0) { - if ((chunk + 1)->fourcc == YY_FOUR_CC('I', 'D', 'A', 'T')) { - first_frame_cover = true; - } - } - fcTL_num++; - } break; - case YY_FOUR_CC('f', 'd', 'A', 'T'): { // apng data - if (prev_fourcc != YY_FOUR_CC('f', 'd', 'A', 'T') && prev_fourcc != YY_FOUR_CC('f', 'c', 'T', 'L')) { - return false; - } - } break; - } - prev_fourcc = chunk->fourcc; - } - if (IHDR_num != 1) return false; - if (IDAT_num == 0) return false; - if (acTL_num != 1) return false; - if (fcTL_num < acTL_num) return false; - *first_idat_index = first_IDAT; - *first_frame_is_cover = first_frame_cover; - return true; -} - static void yy_png_info_release(yy_png_info *info) { if (info) { if (info->chunks) free(info->chunks); @@ -419,6 +343,7 @@ static void yy_png_info_release(yy_png_info *info) { uint32_t chunk_realloc_num = 16; yy_png_chunk_info *chunks = malloc(sizeof(yy_png_chunk_info) * chunk_realloc_num); if (!chunks) return NULL; + yy_png_info *info = NULL; // parse png chunks uint32_t offset = 8; @@ -429,13 +354,13 @@ static void yy_png_info_release(yy_png_info *info) { int32_t apng_frame_index = 0; int32_t apng_frame_number = -1; bool apng_chunk_error = false; + bool first_frame_is_cover = false; + bool encountered_fdAT = false; + uint32_t first_IDAT_index = 0; do { if (chunk_num >= chunk_capacity) { yy_png_chunk_info *new_chunks = realloc(chunks, sizeof(yy_png_chunk_info) * (chunk_capacity + chunk_realloc_num)); - if (!new_chunks) { - free(chunks); - return NULL; - } + if (!new_chunks) goto fail; chunks = new_chunks; chunk_capacity += chunk_realloc_num; } @@ -443,20 +368,28 @@ static void yy_png_info_release(yy_png_info *info) { const uint8_t *chunk_data = data + offset; chunk->offset = offset; chunk->length = yy_swap_endian_uint32(*((uint32_t *)chunk_data)); - if ((uint64_t)chunk->offset + (uint64_t)chunk->length + 12 > length) { - free(chunks); - return NULL; - } + if ((uint64_t)chunk->offset + (uint64_t)chunk->length + 12 > length) goto fail; chunk->fourcc = *((uint32_t *)(chunk_data + 4)); if ((uint64_t)chunk->offset + 4 + chunk->length + 4 > (uint64_t)length) break; chunk->crc32 = yy_swap_endian_uint32(*((uint32_t *)(chunk_data + 8 + chunk->length))); - chunk_num++; offset += 12 + chunk->length; - + if (chunk_num == 0 && chunk->fourcc != YY_FOUR_CC('I', 'H', 'D', 'R') || + offset == length && chunk->fourcc != YY_FOUR_CC('I', 'E', 'N', 'D')) { + goto fail; + } + if (chunk->fourcc == YY_FOUR_CC('t', 'E', 'X', 't') || + chunk->fourcc == YY_FOUR_CC('z', 'T', 'X', 't') || + chunk->fourcc == YY_FOUR_CC('i', 'T', 'X', 't') || + chunk->fourcc == YY_FOUR_CC('t', 'I', 'M', 'E') || + chunk->fourcc == YY_FOUR_CC('e', 'X', 'I', 'f')) { + continue; + } + chunk_num++; + switch (chunk->fourcc) { case YY_FOUR_CC('a', 'c', 'T', 'L') : { - if (chunk->length == 8) { + if (apng_frame_number == -1 && first_IDAT_index == 0 && chunk->length == 8) { apng_frame_number = yy_swap_endian_uint32(*((uint32_t *)(chunk_data + 8))); apng_loop_num = yy_swap_endian_uint32(*((uint32_t *)(chunk_data + 12))); } else { @@ -466,11 +399,17 @@ case YY_FOUR_CC('a', 'c', 'T', 'L') : { case YY_FOUR_CC('f', 'c', 'T', 'L') : case YY_FOUR_CC('f', 'd', 'A', 'T') : { if (chunk->fourcc == YY_FOUR_CC('f', 'c', 'T', 'L')) { - if (chunk->length != 26) { - apng_chunk_error = true; - } else { + if ((apng_frame_index > 0 ? encountered_fdAT : 1) && chunk->length == 26) { apng_frame_index++; + encountered_fdAT = false; + } else { + apng_chunk_error = true; } + } else if (apng_sequence_index == -1 || first_IDAT_index == 0 || + (apng_sequence_index == 0 && first_frame_is_cover)) { + apng_chunk_error = true; + } else { + encountered_fdAT = true; } if (chunk->length > 4) { uint32_t sequence = yy_swap_endian_uint32(*((uint32_t *)(chunk_data + 8))); @@ -482,52 +421,48 @@ case YY_FOUR_CC('f', 'd', 'A', 'T') : { } else { apng_chunk_error = true; } + if (apng_sequence_index == 0 && first_IDAT_index == 0) { + first_frame_is_cover = true; + } + } break; + case YY_FOUR_CC('I', 'D', 'A', 'T') : { + if (first_IDAT_index == 0) { + first_IDAT_index = chunk_num - 1; + } else if ((chunk - 1)->fourcc != YY_FOUR_CC('I', 'D', 'A', 'T')) { + goto fail; + } + encountered_fdAT = true; + } break; + case YY_FOUR_CC('I', 'H', 'D', 'R') : { + if (chunk->length != 13 || chunk_num != 1) goto fail; } break; case YY_FOUR_CC('I', 'E', 'N', 'D') : { + if (chunk->length != 0) goto fail; offset = length; // end, break do-while loop } break; } } while (offset + 12 <= length); - if (chunk_num < 3 || - chunks->fourcc != YY_FOUR_CC('I', 'H', 'D', 'R') || - chunks->length != 13) { - free(chunks); - return NULL; - } + if (first_IDAT_index == 0 || offset != length) goto fail; + if (!encountered_fdAT) apng_chunk_error = true; // png info - yy_png_info *info = calloc(1, sizeof(yy_png_info)); - if (!info) { - free(chunks); - return NULL; - } + info = calloc(1, sizeof(yy_png_info)); + if (!info) goto fail; info->chunks = chunks; info->chunk_num = chunk_num; yy_png_chunk_IHDR_read(&info->header, data + chunks->offset + 8); // apng info if (!apng_chunk_error && apng_frame_number == apng_frame_index && apng_frame_number >= 1) { - bool first_frame_is_cover = false; - uint32_t first_IDAT_index = 0; - if (!yy_png_validate_animation_chunk_order(info->chunks, info->chunk_num, &first_IDAT_index, &first_frame_is_cover)) { - return info; // ignore apng chunk - } - info->apng_loop_num = apng_loop_num; info->apng_frame_num = apng_frame_number; info->apng_first_frame_is_cover = first_frame_is_cover; info->apng_shared_insert_index = first_IDAT_index; info->apng_frames = calloc(apng_frame_number, sizeof(yy_png_frame_info)); - if (!info->apng_frames) { - yy_png_info_release(info); - return NULL; - } + if (!info->apng_frames) goto fail; info->apng_shared_chunk_indexs = calloc(info->chunk_num, sizeof(uint32_t)); - if (!info->apng_shared_chunk_indexs) { - yy_png_info_release(info); - return NULL; - } + if (!info->apng_shared_chunk_indexs) goto fail; int32_t frame_index = -1; uint32_t *shared_chunk_index = info->apng_shared_chunk_indexs; @@ -535,11 +470,11 @@ case YY_FOUR_CC('I', 'E', 'N', 'D') : { yy_png_chunk_info *chunk = info->chunks + i; switch (chunk->fourcc) { case YY_FOUR_CC('I', 'D', 'A', 'T'): { - if (info->apng_shared_insert_index == 0) { - info->apng_shared_insert_index = i; - } if (first_frame_is_cover) { yy_png_frame_info *frame = info->apng_frames + frame_index; + if (frame->chunk_index == 0) { + frame->chunk_index = i; + } frame->chunk_num++; frame->chunk_size += chunk->length + 12; } @@ -549,24 +484,33 @@ case YY_FOUR_CC('a', 'c', 'T', 'L'): { case YY_FOUR_CC('f', 'c', 'T', 'L'): { frame_index++; yy_png_frame_info *frame = info->apng_frames + frame_index; - frame->chunk_index = i + 1; yy_png_chunk_fcTL_read(&frame->frame_control, data + chunk->offset + 8); } break; case YY_FOUR_CC('f', 'd', 'A', 'T'): { yy_png_frame_info *frame = info->apng_frames + frame_index; + if (frame->chunk_index == 0) { + frame->chunk_index = i; + } frame->chunk_num++; frame->chunk_size += chunk->length + 12; } break; default: { - *shared_chunk_index = i; - shared_chunk_index++; - info->apng_shared_chunk_size += chunk->length + 12; - info->apng_shared_chunk_num++; + if (i < first_IDAT_index || i == info->chunk_num - 1) { + *shared_chunk_index = i; + shared_chunk_index++; + info->apng_shared_chunk_size += chunk->length + 12; + info->apng_shared_chunk_num++; + } } break; } } } return info; + +fail: + if (info) yy_png_info_release(info); + else if (chunks) free(chunks); + return NULL; } /** @@ -604,8 +548,8 @@ case YY_FOUR_CC('f', 'd', 'A', 'T'): { if (shared_chunk_index >= info->apng_shared_insert_index && !inserted) { // replace IDAT with fdAT inserted = true; - for (uint32_t c = 0; c < frame_info->chunk_num; c++) { - yy_png_chunk_info *insert_chunk_info = info->chunks + frame_info->chunk_index + c; + yy_png_chunk_info *insert_chunk_info = info->chunks + frame_info->chunk_index; + for (uint32_t c = frame_info->chunk_num; c > 0; insert_chunk_info++) { if (insert_chunk_info->fourcc == YY_FOUR_CC('f', 'd', 'A', 'T')) { *((uint32_t *)(frame_data + data_offset)) = yy_swap_endian_uint32(insert_chunk_info->length - 4); *((uint32_t *)(frame_data + data_offset + 4)) = YY_FOUR_CC('I', 'D', 'A', 'T'); @@ -613,9 +557,11 @@ case YY_FOUR_CC('f', 'd', 'A', 'T'): { uint32_t crc = (uint32_t)crc32(0, frame_data + data_offset + 4, insert_chunk_info->length); *((uint32_t *)(frame_data + data_offset + insert_chunk_info->length + 4)) = yy_swap_endian_uint32(crc); data_offset += insert_chunk_info->length + 8; - } else { // IDAT + c--; + } else if (insert_chunk_info->fourcc == YY_FOUR_CC('I', 'D', 'A', 'T')) { memcpy(frame_data + data_offset, data + insert_chunk_info->offset, insert_chunk_info->length + 12); data_offset += insert_chunk_info->length + 12; + c--; } } }