[devel] IDAT compression failed if preceded by a compressed text chunk

This was because the attempt to reset the zlib stream in png_write_IDAT
happened after the first IDAT chunk had been deflated - much too late.
In this change internal functions are added to claim/release the z_stream
and, hopefully, make the code more robust.  Also deflateEnd checking is
added - previously libpng would ignore an error at the end of the stream.
This commit is contained in:
John Bowler 2011-05-05 17:35:39 -05:00 committed by Glenn Randers-Pehrson
parent c559bb58ed
commit c5bef946b1
9 changed files with 203 additions and 104 deletions

View File

@ -89,6 +89,13 @@ Version 1.5.3beta05 [May 5, 2011]
PNG_NO_WARNINGS does actually work now.
Added PNG_WRITE_OPTIMIZE_CMF_SUPPORTED macro to make the zlib "CMF" byte
optimization configureable.
IDAT compression failed if preceded by a compressed text chunk
This was because the attempt to reset the zlib stream in png_write_IDAT
happened after the first IDAT chunk had been deflated - much too late.
In this change internal functions are added to claim/release the z_stream
and, hopefully, make the code more robust. Also deflateEnd checking is
added - previously libpng would ignore an error at the end of the stream.
Send comments/corrections/commendations to png-mng-implement at lists.sf.net:
(subscription required; visit

View File

@ -3348,6 +3348,12 @@ Version 1.5.3beta05 [May 5, 2011]
PNG_NO_WARNINGS does actually work now.
Added PNG_WRITE_OPTIMIZE_CMF_SUPPORTED macro to make the zlib "CMF" byte
optimization configureable.
IDAT compression failed if preceded by a compressed text chunk
This was because the attempt to reset the zlib stream in png_write_IDAT
happened after the first IDAT chunk had been deflated - much too late.
In this change internal functions are added to claim/release the z_stream
and, hopefully, make the code more robust. Also deflateEnd checking is
added - previously libpng would ignore an error at the end of the stream.
Send comments/corrections/commendations to png-mng-implement at lists.sf.net
(subscription required; visit

2
png.h
View File

@ -1394,6 +1394,7 @@ PNG_FIXED_EXPORT(209, void, png_set_filter_heuristics_fixed,
#define PNG_FILTER_HEURISTIC_WEIGHTED 2 /* Experimental feature */
#define PNG_FILTER_HEURISTIC_LAST 3 /* Not a valid value */
#ifdef PNG_WRITE_SUPPORTED
/* Set the library compression level. Currently, valid values range from
* 0 - 9, corresponding directly to the zlib compression levels 0 - 9
* (0 - no compression, 9 - "maximal" compression). Note that tests have
@ -1415,6 +1416,7 @@ PNG_EXPORT(72, void, png_set_compression_window_bits, (png_structp png_ptr,
PNG_EXPORT(73, void, png_set_compression_method, (png_structp png_ptr,
int method));
#endif
#ifdef PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION
/* Also set zlib parameters for compressing non-IDAT chunks */

View File

@ -102,7 +102,6 @@ png_err,(png_structp png_ptr),PNG_NORETURN)
}
#endif /* PNG_ERROR_TEXT_SUPPORTED */
#if defined(PNG_WARNINGS_SUPPORTED) || defined(PNG_TIME_RFC1123_SUPPORTED)
/* Utility to safely appends strings to a buffer. This never errors out so
* error checking is not required in the caller.
*/
@ -112,8 +111,9 @@ png_safecat(png_charp buffer, size_t bufsize, size_t pos,
{
if (buffer != NULL && pos < bufsize)
{
if (string != NULL) while (*string != '\0' && pos < bufsize-1)
buffer[pos++] = *string++;
if (string != NULL)
while (*string != '\0' && pos < bufsize-1)
buffer[pos++] = *string++;
buffer[pos] = '\0';
}
@ -121,6 +121,7 @@ png_safecat(png_charp buffer, size_t bufsize, size_t pos,
return pos;
}
#if defined(PNG_WARNINGS_SUPPORTED) || defined(PNG_TIME_RFC1123_SUPPORTED)
/* Utility to dump an unsigned value into a buffer, given a start pointer and
* and end pointer (which should point just *beyond* the end of the buffer!)
* Returns the pointer to the start of the formatted string.

View File

@ -276,7 +276,6 @@ typedef PNG_CONST png_uint_16p FAR * png_const_uint_16pp;
#define PNG_BACKGROUND_IS_GRAY 0x800
#define PNG_HAVE_PNG_SIGNATURE 0x1000
#define PNG_HAVE_CHUNK_AFTER_IDAT 0x2000 /* Have another chunk after IDAT */
#define PNG_ZLIB_READY_FOR_ZTXT 0x4000 /* 1: ready for ZTXT; 0: for IDAT */
/* Flags for the transformations the PNG library does on the image data */
#define PNG_BGR 0x0001
@ -740,9 +739,6 @@ PNG_EXTERN void png_read_filter_row PNGARG((png_structp png_ptr,
PNG_EXTERN void png_write_find_filter PNGARG((png_structp png_ptr,
png_row_infop row_info));
/* Write out the filtered row. */
PNG_EXTERN void png_write_filtered_row PNGARG((png_structp png_ptr,
png_bytep filtered_row));
/* Finish a row while reading, dealing with interlacing passes, etc. */
PNG_EXTERN void png_read_finish_row PNGARG((png_structp png_ptr));
@ -1089,10 +1085,6 @@ PNG_EXTERN PNG_FUNCTION(void, png_fixed_error, (png_structp png_ptr,
png_const_charp name),PNG_NORETURN);
#endif
/* Various internal functions to handle formatted warning messages, currently
* only implemented for warnings.
*/
#if defined(PNG_WARNINGS_SUPPORTED) || defined(PNG_TIME_RFC1123_SUPPORTED)
/* Puts 'string' into 'buffer' at buffer[pos], taking care never to overwrite
* the end. Always leaves the buffer nul terminated. Never errors out (and
* there is no error code.)
@ -1100,6 +1092,10 @@ PNG_EXTERN PNG_FUNCTION(void, png_fixed_error, (png_structp png_ptr,
PNG_EXTERN size_t png_safecat(png_charp buffer, size_t bufsize, size_t pos,
png_const_charp string);
/* Various internal functions to handle formatted warning messages, currently
* only implemented for warnings.
*/
#if defined(PNG_WARNINGS_SUPPORTED) || defined(PNG_TIME_RFC1123_SUPPORTED)
/* Utility to dump an unsigned value into a buffer, given a start pointer and
* and end pointer (which should point just *beyond* the end of the buffer!)
* Returns the pointer to the start of the formatted string. This utility only

View File

@ -66,11 +66,26 @@ struct png_struct_def
z_stream zstream; /* pointer to decompression structure (below) */
png_bytep zbuf; /* buffer for zlib */
uInt zbuf_size; /* size of zbuf (typically 65536) */
#ifdef PNG_WRITE_SUPPORTED
/* Added in 1.5.3: state to keep track of whether the zstream has been
* initialized and if so whether it is for IDAT or some other chunk.
*/
#define PNG_ZLIB_UNINITIALIZED 0
#define PNG_ZLIB_FOR_IDAT 1
#define PNG_ZLIB_FOR_TEXT 2 /* anything other than IDAT */
#define PNG_ZLIB_USE_MASK 3 /* bottom two bits */
#define PNG_ZLIB_IN_USE 4 /* a flag value */
png_uint_32 zlib_state; /* State of zlib initialization */
/* End of material added at libpng 1.5.3 */
int zlib_level; /* holds zlib compression level */
int zlib_method; /* holds zlib compression method */
int zlib_window_bits; /* holds zlib compression window bits */
int zlib_mem_level; /* holds zlib compression memory level */
int zlib_strategy; /* holds zlib compression strategy */
#endif
/* Added at libpng 1.5.3 */
#if defined(PNG_WRITE_COMPRESSED_TEXT_SUPPORTED) || \
defined(PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION)

View File

@ -844,8 +844,6 @@ png_write_flush(png_structp png_ptr)
{
/* Write the IDAT and reset the zlib output buffer */
png_write_IDAT(png_ptr, png_ptr->zbuf, png_ptr->zbuf_size);
png_ptr->zstream.next_out = png_ptr->zbuf;
png_ptr->zstream.avail_out = (uInt)png_ptr->zbuf_size;
wrote_IDAT = 1;
}
} while (wrote_IDAT == 1);
@ -856,8 +854,6 @@ png_write_flush(png_structp png_ptr)
/* Write the IDAT and reset the zlib output buffer */
png_write_IDAT(png_ptr, png_ptr->zbuf,
png_ptr->zbuf_size - png_ptr->zstream.avail_out);
png_ptr->zstream.next_out = png_ptr->zbuf;
png_ptr->zstream.avail_out = (uInt)png_ptr->zbuf_size;
}
png_ptr->flush_rows = 0;
png_flush(png_ptr);
@ -954,7 +950,8 @@ png_write_destroy(png_structp png_ptr)
png_debug(1, "in png_write_destroy");
/* Free any memory zlib uses */
deflateEnd(&png_ptr->zstream);
if (png_ptr->zlib_state != PNG_ZLIB_UNINITIALIZED)
deflateEnd(&png_ptr->zstream);
/* Free our memory. png_free checks NULL for us. */
png_free(png_ptr, png_ptr->zbuf);

View File

@ -192,6 +192,148 @@ png_write_chunk_end(png_structp png_ptr)
png_write_data(png_ptr, buf, (png_size_t)4);
}
/* Initialize the compressor for the appropriate type of compression. */
static void
png_zlib_claim(png_structp png_ptr, png_uint_32 state)
{
if (!(png_ptr->zlib_state & PNG_ZLIB_IN_USE))
{
/* If already initialized for 'state' do not re-init. */
if (png_ptr->zlib_state != state)
{
int ret = Z_OK;
png_const_charp who = "-";
/* If actually initialized for another state do a deflateEnd. */
if (png_ptr->zlib_state != PNG_ZLIB_UNINITIALIZED)
{
ret = deflateEnd(&png_ptr->zstream);
who = "end";
png_ptr->zlib_state = PNG_ZLIB_UNINITIALIZED;
}
/* zlib itself detects an incomplete state on deflateEnd */
if (ret == Z_OK) switch (state)
{
# ifdef PNG_WRITE_COMPRESSED_TEXT_SUPPORTED
case PNG_ZLIB_FOR_TEXT:
ret = deflateInit2(&png_ptr->zstream,
png_ptr->zlib_text_level, png_ptr->zlib_text_method,
png_ptr->zlib_text_window_bits,
png_ptr->zlib_text_mem_level, png_ptr->zlib_text_strategy);
who = "text";
break;
# endif
case PNG_ZLIB_FOR_IDAT:
ret = deflateInit2(&png_ptr->zstream, png_ptr->zlib_level,
png_ptr->zlib_method, png_ptr->zlib_window_bits,
png_ptr->zlib_mem_level, png_ptr->zlib_strategy);
who = "IDAT";
break;
default:
png_error(png_ptr, "invalid zlib state");
}
if (ret == Z_OK)
png_ptr->zlib_state = state;
else /* an error in deflateEnd or deflateInit2 */
{
size_t pos = 0;
char msg[64];
pos = png_safecat(msg, sizeof msg, pos,
"zlib failed to initialize compressor (");
pos = png_safecat(msg, sizeof msg, pos, who);
switch (ret)
{
case Z_VERSION_ERROR:
pos = png_safecat(msg, sizeof msg, pos, ") version error");
break;
case Z_STREAM_ERROR:
pos = png_safecat(msg, sizeof msg, pos, ") stream error");
break;
case Z_MEM_ERROR:
pos = png_safecat(msg, sizeof msg, pos, ") memory error");
break;
default:
pos = png_safecat(msg, sizeof msg, pos, ") unknown error");
break;
}
png_error(png_ptr, msg);
}
}
/* Here on success, claim the zstream: */
png_ptr->zlib_state |= PNG_ZLIB_IN_USE;
}
else
png_error(png_ptr, "zstream already in use (internal error)");
}
/* The opposite: release the stream. It is also reset, this API will warn on
* error but will not fail.
*/
static void
png_zlib_release(png_structp png_ptr)
{
if (png_ptr->zlib_state & PNG_ZLIB_IN_USE)
{
int ret = deflateReset(&png_ptr->zstream);
png_ptr->zlib_state &= ~PNG_ZLIB_IN_USE;
if (ret != Z_OK)
{
png_const_charp err;
png_warning_parameters p;
switch (ret)
{
case Z_VERSION_ERROR:
err = "version";
break;
case Z_STREAM_ERROR:
err = "stream";
break;
case Z_MEM_ERROR:
err = "memory";
break;
default:
err = "unknown";
break;
}
png_warning_parameter_unsigned(p, 1, PNG_NUMBER_FORMAT_d, ret);
png_warning_parameter(p, 2, err);
if (png_ptr->zstream.msg)
err = png_ptr->zstream.msg;
else
err = "[no zlib message]";
png_warning_parameter(p, 3, err);
png_formatted_warning(png_ptr, p,
"zlib failed to reset compressor: @1(@2): @3");
}
}
else
png_warning(png_ptr, "zstream not in use (internal error)");
}
#ifdef PNG_WRITE_COMPRESSED_TEXT_SUPPORTED
/* This pair of functions encapsulates the operation of (a) compressing a
* text string, and (b) issuing it later as a series of chunk data writes.
@ -252,37 +394,7 @@ png_text_compress(png_structp png_ptr,
* data, or if the input string is incredibly large (although this
* wouldn't cause a failure, just a slowdown due to swapping).
*/
if (!(png_ptr->mode & PNG_ZLIB_READY_FOR_ZTXT))
{
/* png_warning(png_ptr, "Initialize compressor for ztxt"); */
/* Free memory from previously opened zstream */
deflateEnd(&png_ptr->zstream);
/* Initialize the compressor for zTXt compression. */
ret = deflateInit2(&png_ptr->zstream, png_ptr->zlib_text_level,
png_ptr->zlib_text_method, png_ptr->zlib_text_window_bits,
png_ptr->zlib_text_mem_level, png_ptr->zlib_text_strategy);
if (ret != Z_OK)
{
if (ret == Z_VERSION_ERROR)
png_error(png_ptr,
"zlib failed to initialize compressor for text-- version error");
if (ret == Z_STREAM_ERROR)
png_error(png_ptr,
"zlib failed to initialize compressor for text-- stream error");
if (ret == Z_MEM_ERROR)
png_error(png_ptr,
"zlib failed to initialize compressor for text-- mem error");
png_error(png_ptr, "zlib failed to initialize compressor for text");
}
png_ptr->mode |= PNG_ZLIB_READY_FOR_ZTXT;
}
png_zlib_claim(png_ptr, PNG_ZLIB_FOR_TEXT);
/* Set up the compression buffers */
/* TODO: the following cast hides a potential overflow problem. */
@ -527,7 +639,7 @@ png_write_compressed_data_out(png_structp png_ptr, compression_state *comp)
(png_size_t)(png_ptr->zbuf_size - png_ptr->zstream.avail_out));
/* Reset zlib for another zTXt/iTXt or image data */
deflateReset(&png_ptr->zstream);
png_zlib_release(png_ptr);
}
#endif /* PNG_WRITE_COMPRESSED_TEXT_SUPPORTED */
@ -541,7 +653,6 @@ png_write_IHDR(png_structp png_ptr, png_uint_32 width, png_uint_32 height,
int interlace_type)
{
PNG_IHDR;
int ret;
png_byte buf[13]; /* Buffer to store the IHDR info */
@ -747,10 +858,8 @@ png_write_IHDR(png_structp png_ptr, png_uint_32 width, png_uint_32 height,
#endif /* PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION */
#endif /* PNG_WRITE_COMPRESSED_TEXT_SUPPORTED */
/* Initialize the zlib compressor */
ret = deflateInit2(&png_ptr->zstream, png_ptr->zlib_level,
png_ptr->zlib_method, png_ptr->zlib_window_bits,
png_ptr->zlib_mem_level, png_ptr->zlib_strategy);
/* Record that the compressor has not yet been initialized. */
png_ptr->zlib_state = PNG_ZLIB_UNINITIALIZED;
png_ptr->mode = PNG_HAVE_IHDR; /* not READY_FOR_ZTXT */
}
@ -837,58 +946,14 @@ png_write_IDAT(png_structp png_ptr, png_bytep data, png_size_t length)
png_debug(1, "in png_write_IDAT");
#ifdef PNG_WRITE_OPTIMIZE_CMF_SUPPORTED
if (!(png_ptr->mode & PNG_HAVE_IDAT) &&
png_ptr->compression_type == PNG_COMPRESSION_TYPE_BASE)
{
#ifdef PNG_WRITE_OPTIMIZE_CMF_SUPPORTED
unsigned int z_cmf; /* zlib compression method and flags */
#endif /* PNG_WRITE_OPTIMIZE_CMF_SUPPORTED */
#ifdef PNG_WRITE_COMPRESSED_TEXT_SUPPORTED
int ret;
/* Reinitialize the compressor if necessary */
if (png_ptr->mode & PNG_ZLIB_READY_FOR_ZTXT)
{
/* Free memory from previously opened zstream */
deflateEnd(&png_ptr->zstream);
ret = deflateInit2(&png_ptr->zstream, png_ptr->zlib_level,
png_ptr->zlib_method, png_ptr->zlib_window_bits,
png_ptr->zlib_mem_level, png_ptr->zlib_strategy);
if (ret != Z_OK)
{
if (ret == Z_VERSION_ERROR)
png_error(png_ptr,
"zlib failed to initialize compressor -- version error");
if (ret == Z_STREAM_ERROR)
png_error(png_ptr,
"zlib failed to initialize compressor -- stream error");
if (ret == Z_MEM_ERROR)
png_error(png_ptr,
"zlib failed to initialize compressor -- mem error");
png_error(png_ptr, "zlib failed to initialize compressor");
}
png_ptr->mode &= ~PNG_ZLIB_READY_FOR_ZTXT; /* Ready for IDAT */
}
#endif /* PNG_WRITE_COMPRESSED_TEXT_SUPPORTED */
png_ptr->zstream.next_out = png_ptr->zbuf;
png_ptr->zstream.avail_out = (uInt)png_ptr->zbuf_size;
/* libpng is not interested in zstream.data_type, so set it
* to a predefined value, to avoid its evaluation inside zlib
*/
png_ptr->zstream.data_type = Z_BINARY;
#ifdef PNG_WRITE_OPTIMIZE_CMF_SUPPORTED
/* Optimize the CMF field in the zlib stream. This hack of the zlib
* stream is compliant to the stream specification.
*/
z_cmf = data[0];
unsigned int z_cmf = data[0]; /* zlib compression method and flags */
if ((z_cmf & 0x0f) == 8 && (z_cmf & 0xf0) <= 0x70)
{
@ -946,11 +1011,18 @@ png_write_IDAT(png_structp png_ptr, png_bytep data, png_size_t length)
else
png_error(png_ptr,
"Invalid zlib compression method or flags in IDAT");
#endif /* PNG_WRITE_OPTIMIZE_CMF_SUPPORTED */
}
#endif /* PNG_WRITE_OPTIMIZE_CMF_SUPPORTED */
png_write_chunk(png_ptr, png_IDAT, data, length);
png_ptr->mode |= PNG_HAVE_IDAT;
/* Prior to 1.5.3 this code was replicated in every caller (except at the
* end, where it isn't technically necessary). Since this function has
* flushed the data we can safely reset the zlib output buffer here.
*/
png_ptr->zstream.next_out = png_ptr->zbuf;
png_ptr->zstream.avail_out = (uInt)png_ptr->zbuf_size;
}
/* Write an IEND chunk */
@ -2029,6 +2101,7 @@ png_write_start_row(png_structp png_ptr)
png_ptr->usr_width = png_ptr->width;
}
png_zlib_claim(png_ptr, PNG_ZLIB_FOR_IDAT);
png_ptr->zstream.avail_out = (uInt)png_ptr->zbuf_size;
png_ptr->zstream.next_out = png_ptr->zbuf;
}
@ -2150,7 +2223,7 @@ png_write_finish_row(png_structp png_ptr)
png_ptr->zstream.avail_out);
}
deflateReset(&png_ptr->zstream);
png_zlib_release(png_ptr);
png_ptr->zstream.data_type = Z_BINARY;
}
@ -2339,6 +2412,8 @@ png_do_write_interlace(png_row_infop row_info, png_bytep row, int pass)
* been specified by the application, and then writes the row out with the
* chosen filter.
*/
static void png_write_filtered_row(png_structp png_ptr, png_bytep filtered_row);
#define PNG_MAXSUM (((png_uint_32)(-1)) >> 1)
#define PNG_HISHIFT 10
#define PNG_LOMASK ((png_uint_32)0xffffL)
@ -3014,7 +3089,7 @@ png_write_find_filter(png_structp png_ptr, png_row_infop row_info)
/* Do the actual writing of a previously filtered row. */
void /* PRIVATE */
static void
png_write_filtered_row(png_structp png_ptr, png_bytep filtered_row)
{
png_size_t avail;
@ -3074,8 +3149,6 @@ png_write_filtered_row(png_structp png_ptr, png_bytep filtered_row)
{
/* Write the IDAT and reset the zlib output buffer */
png_write_IDAT(png_ptr, png_ptr->zbuf, png_ptr->zbuf_size);
png_ptr->zstream.next_out = png_ptr->zbuf;
png_ptr->zstream.avail_out = (uInt)png_ptr->zbuf_size;
}
/* Repeat until all data has been compressed */
} while (avail > 0 || png_ptr->zstream.avail_in > 0);

View File

@ -553,6 +553,7 @@ option SAVE_INT_32 requires WRITE
# added at libpng-1.5.3
option WRITE_OPTIMIZE_CMF requires WRITE
option READ_COMPRESSED_TEXT disabled
option READ_iCCP enables READ_COMPRESSED_TEXT
option READ_iTXt enables READ_COMPRESSED_TEXT
option READ_zTXt enables READ_COMPRESSED_TEXT
@ -561,6 +562,7 @@ option READ_COMPRESSED_TEXT enables READ_TEXT
option WRITE_oFFs enables SAVE_INT_32
option WRITE_pCAL enables SAVE_INT_32
option WRITE_COMPRESSED_TEXT disabled
option WRITE_iCCP enables WRITE_COMPRESSED_TEXT
option WRITE_iTXt enables WRITE_COMPRESSED_TEXT
option WRITE_zTXt enables WRITE_COMPRESSED_TEXT