diff --git a/ANNOUNCE b/ANNOUNCE index 814d1787a..0181bd04a 100644 --- a/ANNOUNCE +++ b/ANNOUNCE @@ -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 diff --git a/CHANGES b/CHANGES index 1aaa5b1fa..46aa680a2 100644 --- a/CHANGES +++ b/CHANGES @@ -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 diff --git a/png.h b/png.h index 9b131850e..4d1d53c56 100644 --- a/png.h +++ b/png.h @@ -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 */ diff --git a/pngerror.c b/pngerror.c index 20160cf27..eef4ec4f4 100644 --- a/pngerror.c +++ b/pngerror.c @@ -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. diff --git a/pngpriv.h b/pngpriv.h index c18b26cd7..abf3a2edc 100644 --- a/pngpriv.h +++ b/pngpriv.h @@ -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 diff --git a/pngstruct.h b/pngstruct.h index 7dc209c3c..e46831214 100644 --- a/pngstruct.h +++ b/pngstruct.h @@ -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) diff --git a/pngwrite.c b/pngwrite.c index 1eb360b23..1a9ecb76f 100644 --- a/pngwrite.c +++ b/pngwrite.c @@ -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); diff --git a/pngwutil.c b/pngwutil.c index 637f8b367..0b48215c6 100644 --- a/pngwutil.c +++ b/pngwutil.c @@ -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); diff --git a/scripts/pnglibconf.dfa b/scripts/pnglibconf.dfa index 2366f8bb1..8f449628f 100644 --- a/scripts/pnglibconf.dfa +++ b/scripts/pnglibconf.dfa @@ -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