#include "pngpriv.h"
#define PNG_SRC_FILE PNG_SRC_FILE_pngpread
#ifdef PNG_PROGRESSIVE_READ_SUPPORTED
static void PNGCBAPI
png_push_fill_buffer(png_structp png_ptr, png_bytep buffer, png_size_t length)
{
png_bytep ptr;
if (png_ptr == NULL)
return;
ptr = buffer;
debug(length > 0);
if (length > 0 && png_ptr->save_buffer_size > 0)
{
png_size_t save_size;
if (length < png_ptr->save_buffer_size)
save_size = length;
else
save_size = png_ptr->save_buffer_size;
memcpy(ptr, png_ptr->save_buffer_ptr, save_size);
length -= save_size;
ptr += save_size;
png_ptr->buffer_size -= save_size;
png_ptr->save_buffer_size -= save_size;
png_ptr->save_buffer_ptr += save_size;
}
if (length > 0 && png_ptr->current_buffer_size > 0)
{
png_size_t save_size;
if (length < png_ptr->current_buffer_size)
save_size = length;
else
save_size = png_ptr->current_buffer_size;
memcpy(ptr, png_ptr->current_buffer_ptr, save_size);
png_ptr->buffer_size -= save_size;
png_ptr->current_buffer_size -= save_size;
png_ptr->current_buffer_ptr += save_size;
}
}
typedef enum
{
png_read_signature = 0,
png_read_chunk_header,
png_read_end_IDAT,
png_read_done,
png_read_chunk,
png_read_chunk_skip = png_read_chunk+png_chunk_skip,
png_read_chunk_unknown = png_read_chunk+png_chunk_unknown,
png_read_chunk_process_all = png_read_chunk+png_chunk_process_all,
png_read_chunk_process_part = png_read_chunk+png_chunk_process_part
} png_read_mode;
static void
png_push_save_buffer_partial(png_structrp png_ptr, size_t amount)
{
png_bytep buffer;
size_t save_size = png_ptr->save_buffer_size;
if (save_size > PNG_SIZE_MAX - amount)
png_error(png_ptr, "save buffer overflow");
if (png_ptr->save_buffer_max < save_size + amount)
{
buffer = png_voidcast(png_bytep, png_malloc(png_ptr, save_size + amount));
memcpy(buffer, png_ptr->save_buffer_ptr, save_size);
png_free(png_ptr, png_ptr->save_buffer);
png_ptr->save_buffer_ptr = png_ptr->save_buffer = buffer;
}
else if (png_ptr->save_buffer_max -
(png_ptr->save_buffer_ptr - png_ptr->save_buffer) < save_size + amount)
{
buffer = png_ptr->save_buffer;
memmove(buffer, png_ptr->save_buffer_ptr, save_size);
png_ptr->save_buffer_ptr = buffer;
}
else
buffer = png_ptr->save_buffer_ptr;
memcpy(buffer+save_size, png_ptr->current_buffer_ptr, amount);
png_ptr->save_buffer_size = save_size + amount;
png_ptr->current_buffer_ptr += amount;
png_ptr->current_buffer_size -= amount;
png_ptr->buffer_size -= amount;
}
static void
png_push_save_buffer(png_structrp png_ptr)
{
png_push_save_buffer_partial(png_ptr, png_ptr->current_buffer_size);
png_ptr->buffer_size = 0;
}
#define PNG_PUSH_SAVE_BUFFER_IF_FULL \
if (png_ptr->chunk_length + 4 > png_ptr->buffer_size) \
{ png_push_save_buffer(png_ptr); return; }
#define PNG_PUSH_SAVE_BUFFER_IF_LT(N) \
if (png_ptr->buffer_size < N) \
{ png_push_save_buffer(png_ptr); return; }
png_size_t PNGAPI
png_process_data_pause(png_structrp png_ptr, int save)
{
if (png_ptr != NULL)
{
if (save != 0)
png_push_save_buffer(png_ptr);
else
{
png_size_t remaining = png_ptr->buffer_size;
png_ptr->buffer_size = 0;
if (png_ptr->save_buffer_size < remaining)
return remaining - png_ptr->save_buffer_size;
}
}
return 0;
}
png_uint_32 PNGAPI
png_process_data_skip(png_structrp png_ptr)
{
if (png_ptr != NULL && png_ptr->process_mode == png_read_chunk_skip)
{
if (png_ptr->buffer_size != 0)
png_app_error(png_ptr,
"png_process_data_skip called inside png_process_data");
else if (png_ptr->save_buffer_size != 0)
png_app_error(png_ptr, "png_process_data_skip called with saved data");
else
{
png_ptr->process_mode = png_read_chunk_header;
return png_ptr->chunk_length + 4;
}
}
return 0;
}
static void
png_push_restore_buffer(png_structrp png_ptr, png_bytep buffer,
png_size_t buffer_length)
{
png_ptr->current_buffer_size = buffer_length;
png_ptr->buffer_size = buffer_length + png_ptr->save_buffer_size;
png_ptr->current_buffer_ptr = buffer;
}
static void
png_push_read_signature(png_structrp png_ptr, png_inforp info_ptr)
{
unsigned int num_checked = png_ptr->sig_bytes;
unsigned int num_to_check = 8 - num_checked;
if (png_ptr->buffer_size < num_to_check)
num_to_check = (int)png_ptr->buffer_size;
png_push_fill_buffer(png_ptr, &(info_ptr->signature[num_checked]),
num_to_check);
png_ptr->sig_bytes = png_check_byte(png_ptr,
png_ptr->sig_bytes + num_to_check);
if (png_sig_cmp(info_ptr->signature, num_checked, num_to_check))
{
if (num_checked < 4 &&
png_sig_cmp(info_ptr->signature, num_checked, num_to_check - 4))
png_error(png_ptr, "Not a PNG file");
else
png_error(png_ptr, "PNG file corrupted by ASCII conversion");
}
else if (png_ptr->sig_bytes >= 8)
png_ptr->process_mode = png_read_chunk_header;
}
static void
png_push_crc_finish(png_structrp png_ptr)
{
if (png_ptr->chunk_length != 0 && png_ptr->save_buffer_size != 0)
{
png_size_t save_size = png_ptr->save_buffer_size;
png_uint_32 skip_length = png_ptr->chunk_length;
if (skip_length < save_size)
save_size = (png_size_t)skip_length;
else
skip_length = (png_uint_32)save_size;
png_calculate_crc(png_ptr, png_ptr->save_buffer_ptr, save_size);
png_ptr->chunk_length -= skip_length;
png_ptr->buffer_size -= save_size;
png_ptr->save_buffer_size -= save_size;
png_ptr->save_buffer_ptr += save_size;
}
if (png_ptr->chunk_length != 0 && png_ptr->current_buffer_size != 0)
{
png_size_t save_size = png_ptr->current_buffer_size;
png_uint_32 skip_length = png_ptr->chunk_length;
if (skip_length < save_size)
save_size = (png_size_t)skip_length;
else
skip_length = (png_uint_32)save_size;
png_calculate_crc(png_ptr, png_ptr->current_buffer_ptr, save_size);
png_ptr->chunk_length -= skip_length;
png_ptr->buffer_size -= save_size;
png_ptr->current_buffer_size -= save_size;
png_ptr->current_buffer_ptr += save_size;
}
if (png_ptr->chunk_length == 0)
{
PNG_PUSH_SAVE_BUFFER_IF_LT(4)
png_crc_finish(png_ptr, 0);
png_ptr->process_mode = png_read_chunk_header;
}
}
static void
png_push_read_unknown(png_structrp png_ptr, png_inforp info_ptr)
{
png_bytep buffer;
png_uint_32 chunk_length = png_ptr->chunk_length;
if (png_ptr->save_buffer_size > 0)
{
png_size_t save_size = png_ptr->save_buffer_size;
if (save_size < chunk_length)
{
png_push_save_buffer_partial(png_ptr, chunk_length - save_size);
save_size = chunk_length;
}
buffer = png_ptr->save_buffer_ptr;
png_ptr->save_buffer_ptr = buffer+chunk_length;
png_ptr->save_buffer_size = save_size-chunk_length;
png_ptr->buffer_size -= chunk_length;
affirm(png_ptr->buffer_size >= 4);
}
else
{
affirm(png_ptr->current_buffer_size >= chunk_length+4);
buffer = png_ptr->current_buffer_ptr;
png_ptr->current_buffer_ptr = buffer+chunk_length;
png_ptr->current_buffer_size -= chunk_length;
png_ptr->buffer_size -= chunk_length;
}
png_calculate_crc(png_ptr, buffer, chunk_length);
png_crc_finish(png_ptr, 0);
# ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
png_handle_unknown(png_ptr, info_ptr, buffer);
# else
PNG_UNUSED(info_ptr)
# endif
png_ptr->process_mode = png_read_chunk_header;
}
static void
png_push_have_row(png_structrp png_ptr, png_bytep row)
{
if (png_ptr->row_fn != NULL)
{
png_uint_32 row_number = png_ptr->row_number;
png_byte pass = png_ptr->pass;
if (png_ptr->interlaced)
{
# ifdef PNG_READ_INTERLACING_SUPPORTED
if (!png_ptr->do_interlace)
# endif
{
affirm(PNG_ROW_IN_INTERLACE_PASS(row_number, pass) && row != NULL);
row_number = PNG_PASS_ROWS(row_number+1, pass);
affirm(row_number > 0);
--row_number;
}
}
(*(png_ptr->row_fn))(png_ptr, row, row_number, pass);
}
}
static void
png_push_read_sync_zstream(png_structp png_ptr, png_bytep *bufferp,
size_t *buffer_lengthp)
{
png_bytep original_start = *bufferp;
png_alloc_size_t bytes_consumed = png_ptr->zstream.next_in - original_start;
affirm(buffer_lengthp != NULL);
png_calculate_crc(png_ptr, original_start, bytes_consumed);
*bufferp = original_start + bytes_consumed;
affirm(bytes_consumed <= *buffer_lengthp);
*buffer_lengthp -= (size_t)bytes_consumed;
affirm(bytes_consumed <= png_ptr->chunk_length);
png_ptr->chunk_length -= (png_uint_32)bytes_consumed;
affirm(bytes_consumed <= png_ptr->buffer_size);
png_ptr->buffer_size -= (size_t)bytes_consumed;
}
static void
png_push_read_process_IDAT(png_structp png_ptr, png_bytep *bufferp,
size_t *buffer_lengthp)
{
png_alloc_size_t buffer_length;
if (buffer_lengthp != NULL)
buffer_length = *buffer_lengthp;
else
{
affirm(png_ptr->mode & PNG_AFTER_IDAT);
buffer_length = 0;
}
if (buffer_length > png_ptr->chunk_length)
{
buffer_length = png_ptr->chunk_length;
if (buffer_length == 0)
png_ptr->process_mode = png_read_chunk_skip;
}
png_ptr->zstream.next_in = *bufferp;
while (buffer_length > 0 || buffer_lengthp == NULL)
{
if (buffer_length >= ZLIB_IO_MAX)
{
png_ptr->zstream.avail_in = ZLIB_IO_MAX;
buffer_length -= ZLIB_IO_MAX;
}
else
{
png_ptr->zstream.avail_in = (uInt)buffer_length;
buffer_length = 0;
}
if (!png_ptr->zstream_eod)
{
png_bytep row_buffer = NULL;
png_row_op row_op =
png_read_process_IDAT(png_ptr, NULL, NULL, 1);
if (row_op != png_row_incomplete)
{
if (png_ptr->row_number+1 >= png_ptr->height &&
(!png_ptr->interlaced || png_ptr->pass == 6))
png_ptr->zstream_eod = 1;
}
switch (row_op)
{
case png_row_incomplete:
debug(png_ptr->zstream.avail_in == 0);
affirm(buffer_lengthp != NULL);
continue;
case png_row_process:
affirm(buffer_lengthp != NULL || png_ptr->zstream_error);
# ifdef PNG_TRANSFORM_MECH_SUPPORTED
row_buffer = png_ptr->transformed_row;
if (row_buffer == NULL)
# endif
row_buffer = png_ptr->row_buffer;
break;
case png_row_repeat:
# ifdef PNG_READ_INTERLACING_SUPPORTED
if (png_ptr->do_interlace)
{
# ifdef PNG_TRANSFORM_MECH_SUPPORTED
row_buffer = png_ptr->transformed_row;
if (row_buffer == NULL)
# endif
row_buffer = png_ptr->row_buffer;
break;
}
# endif
continue;
case png_row_skip:
# ifdef PNG_READ_INTERLACING_SUPPORTED
if (png_ptr->do_interlace)
break;
# endif
continue;
default:
impossible("not reached");
}
if (buffer_lengthp != NULL)
png_push_read_sync_zstream(png_ptr, bufferp, buffer_lengthp);
png_push_have_row(png_ptr, row_buffer);
if (buffer_lengthp != NULL)
{
if (png_ptr->chunk_length == 0)
png_ptr->process_mode = png_read_chunk_skip;
else if (png_ptr->buffer_size > 0 && *buffer_lengthp > 0)
png_push_read_process_IDAT(png_ptr, bufferp, buffer_lengthp);
return;
}
if (png_ptr->buffer_size == 0)
return;
continue;
}
affirm(png_ptr->zstream_eod);
if (png_ptr->zowner == 0 || png_read_finish_IDAT(png_ptr))
{
debug(png_ptr->zowner == 0 && png_ptr->zstream_ended);
if (buffer_lengthp != NULL)
{
png_push_read_sync_zstream(png_ptr, bufferp, buffer_lengthp);
if (png_ptr->chunk_length > 0)
{
if (!png_ptr->zstream_error)
{
png_chunk_benign_error(png_ptr,
"too much IDAT data (progressive read)");
png_ptr->zstream_error = 1;
}
}
png_ptr->process_mode = png_read_chunk_skip;
}
return;
}
affirm(png_ptr->zowner == png_IDAT && !png_ptr->zstream_ended);
}
png_push_read_sync_zstream(png_ptr, bufferp, buffer_lengthp);
}
static void
png_push_read_IDAT(png_structrp png_ptr)
{
if (png_ptr->save_buffer_size > 0)
{
png_push_read_process_IDAT(png_ptr, &png_ptr->save_buffer_ptr,
&png_ptr->save_buffer_size);
if (png_ptr->save_buffer_size > 0)
return;
if (png_ptr->process_mode != png_read_chunk_process_part ||
png_ptr->buffer_size == 0)
return;
}
if (png_ptr->current_buffer_size > 0)
png_push_read_process_IDAT(png_ptr, &png_ptr->current_buffer_ptr,
&png_ptr->current_buffer_size);
}
static void
png_push_finish_IDAT(png_structrp png_ptr)
{
while (png_ptr->zowner == png_IDAT)
{
png_byte b = 0, *pb = &b;
png_push_read_process_IDAT(png_ptr, &pb, NULL);
if (png_ptr->zowner == 0)
break;
if (png_ptr->buffer_size == 0)
return;
}
png_ptr->process_mode = png_check_bits(png_ptr, png_ptr->process_mode >> 4,
4);
}
static void
png_push_have_info(png_structrp png_ptr, png_inforp info_ptr)
{
if (png_ptr->info_fn != NULL)
(*(png_ptr->info_fn))(png_ptr, info_ptr);
}
static void
png_push_have_end(png_structrp png_ptr, png_inforp info_ptr)
{
if (png_ptr->end_fn != NULL)
(*(png_ptr->end_fn))(png_ptr, info_ptr);
}
static void
png_push_read_chunk_header(png_structrp png_ptr, png_infop info_ptr)
{
unsigned int mode;
png_byte chunk_header[8];
PNG_PUSH_SAVE_BUFFER_IF_LT(8)
png_push_fill_buffer(png_ptr, chunk_header, 8);
png_ptr->chunk_length = png_get_uint_31(png_ptr, chunk_header);
png_ptr->chunk_name = PNG_CHUNK_FROM_STRING(chunk_header+4);
png_reset_crc(png_ptr, chunk_header+4);
png_check_chunk_name(png_ptr, png_ptr->chunk_name);
png_check_chunk_length(png_ptr, png_ptr->chunk_length);
mode = png_ptr->mode;
png_ptr->process_mode = png_check_bits(png_ptr,
png_read_chunk+png_find_chunk_op(png_ptr), 4);
if ((mode ^ png_ptr->mode) & PNG_HAVE_IDAT)
png_push_have_info(png_ptr, info_ptr);
else if (((mode ^ png_ptr->mode) & PNG_AFTER_IDAT) != 0)
png_ptr->process_mode = png_check_bits(png_ptr,
(png_ptr->process_mode << 4) + png_read_end_IDAT, 8);
}
static void
png_process_some_data(png_structrp png_ptr, png_inforp info_ptr)
{
if (png_ptr == NULL)
return;
switch (png_ptr->process_mode & 0xf)
{
case png_read_signature:
png_push_read_signature(png_ptr, info_ptr);
return;
case png_read_chunk_header:
png_push_read_chunk_header(png_ptr, info_ptr);
return;
case png_read_chunk_skip:
png_push_crc_finish(png_ptr);
return;
case png_read_chunk_unknown:
PNG_PUSH_SAVE_BUFFER_IF_FULL
png_push_read_unknown(png_ptr, info_ptr);
return;
case png_read_chunk_process_all:
PNG_PUSH_SAVE_BUFFER_IF_FULL
png_handle_chunk(png_ptr, info_ptr);
if (png_ptr->mode & PNG_HAVE_IEND)
{
png_ptr->process_mode = png_read_done;
png_push_have_end(png_ptr, info_ptr);
png_ptr->buffer_size = 0;
}
else
png_ptr->process_mode = png_read_chunk_header;
return;
case png_read_chunk_process_part:
debug(png_ptr->chunk_name == png_IDAT &&
(png_ptr->mode & PNG_HAVE_IDAT) &&
!(png_ptr->mode & PNG_AFTER_IDAT));
if (png_ptr->zowner == 0 && !png_ptr->zstream_ended)
png_read_start_IDAT(png_ptr);
png_push_read_IDAT(png_ptr);
return;
case png_read_end_IDAT:
png_push_finish_IDAT(png_ptr);
return;
case png_read_done:
png_app_error(png_ptr, "read beyond end of stream");
png_ptr->buffer_size = 0;
return;
default:
impossible("invalid process mode");
}
}
void PNGAPI
png_process_data(png_structrp png_ptr, png_inforp info_ptr,
png_bytep buffer, png_size_t buffer_size)
{
if (png_ptr == NULL || info_ptr == NULL)
return;
png_push_restore_buffer(png_ptr, buffer, buffer_size);
while (png_ptr->buffer_size)
png_process_some_data(png_ptr, info_ptr);
}
void PNGAPI
png_set_progressive_read_fn(png_structrp png_ptr, png_voidp progressive_ptr,
png_progressive_info_ptr info_fn, png_progressive_row_ptr row_fn,
png_progressive_end_ptr end_fn)
{
if (png_ptr == NULL)
return;
png_ptr->info_fn = info_fn;
png_ptr->row_fn = row_fn;
png_ptr->end_fn = end_fn;
png_set_read_fn(png_ptr, progressive_ptr, png_push_fill_buffer);
}
png_voidp PNGAPI
png_get_progressive_ptr(png_const_structrp png_ptr)
{
if (png_ptr == NULL)
return (NULL);
return png_ptr->io_ptr;
}
#endif