[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.
diff --git a/ANNOUNCE b/ANNOUNCE
index 814d178..0181bd0 100644
--- a/ANNOUNCE
+++ b/ANNOUNCE
@@ -89,6 +89,13 @@
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 1aaa5b1..46aa680 100644
--- a/CHANGES
+++ b/CHANGES
@@ -3348,6 +3348,12 @@
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 9b13185..4d1d53c 100644
--- a/png.h
+++ b/png.h
@@ -1394,6 +1394,7 @@
#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(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 20160cf..eef4ec4 100644
--- a/pngerror.c
+++ b/pngerror.c
@@ -102,7 +102,6 @@
}
#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 @@
{
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 @@
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 c18b26c..abf3a2e 100644
--- a/pngpriv.h
+++ b/pngpriv.h
@@ -276,7 +276,6 @@
#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_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_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 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 7dc209c..e468312 100644
--- a/pngstruct.h
+++ b/pngstruct.h
@@ -66,11 +66,26 @@
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 1eb360b..1a9ecb7 100644
--- a/pngwrite.c
+++ b/pngwrite.c
@@ -844,8 +844,6 @@
{
/* 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 @@
/* 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_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 637f8b3..0b48215 100644
--- a/pngwutil.c
+++ b/pngwutil.c
@@ -192,6 +192,148 @@
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 @@
* 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_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 @@
int interlace_type)
{
PNG_IHDR;
- int ret;
png_byte buf[13]; /* Buffer to store the IHDR info */
@@ -747,10 +858,8 @@
#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_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 @@
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_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_ptr->zstream.avail_out);
}
- deflateReset(&png_ptr->zstream);
+ png_zlib_release(png_ptr);
png_ptr->zstream.data_type = Z_BINARY;
}
@@ -2339,6 +2412,8 @@
* 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 @@
/* 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 @@
{
/* 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 2366f8b..8f44962 100644
--- a/scripts/pnglibconf.dfa
+++ b/scripts/pnglibconf.dfa
@@ -553,6 +553,7 @@
# 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 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