Commit 9d314c3 for zlib

commit 9d314c341b2ce16e01ae44e27ec3dd2f32aa58fd
Author: Mark Adler <git@madler.net>
Date:   Sun Oct 25 10:08:02 2026 -0700

    Retain and deliver buffered data on a gzip read error.

    Previously an error would immediately discard the data decompressed
    so far. This commit allows the decompressed data to be delivered,
    upon completion of which the error is then reported. This corrects
    the code to match the descriptions in zlib.h: "data is returned
    until exhausted". The error is saved in the status, and so is
    available immediately using gzerror, even as data continues to be
    delivered.

diff --git a/gzlib.c b/gzlib.c
index a85e99e..29cb25a 100644
--- a/gzlib.c
+++ b/gzlib.c
@@ -559,10 +559,6 @@ void ZLIB_INTERNAL gz_error(gz_statep state, int err, const char *msg) {
         state->msg = NULL;
     }

-    /* if fatal, set state->x.have to 0 so that the gzgetc() macro fails */
-    if (err != Z_OK && err != Z_BUF_ERROR && !state->again)
-        state->x.have = 0;
-
     /* set error code, and if no message, then done */
     state->err = err;
     if (msg == NULL)
diff --git a/gzread.c b/gzread.c
index 5641b3c..747b7e8 100644
--- a/gzread.c
+++ b/gzread.c
@@ -255,6 +255,11 @@ local int gz_decomp(gz_statep state) {
 local int gz_fetch(gz_statep state) {
     z_streamp strm = &(state->strm);

+    /* after a serious error, only what is already in the output buffer is
+       delivered -- don't try to decompress any more */
+    if (state->err != Z_OK && state->err != Z_BUF_ERROR && !state->again)
+        return -1;
+
     do {
         switch(state->how) {
         case LOOK:      /* -> LOOK, COPY (only if never GZIP), or GZIP */
@@ -416,16 +421,21 @@ int ZEXPORT gzread(gzFile file, voidp buf, unsigned len) {
     if (state->mode != GZ_READ)
         return -1;

-    /* check that there was no (serious) error */
-    if (state->err != Z_OK && state->err != Z_BUF_ERROR && !state->again)
+    /* check that there was no (serious) error -- if there was, deliver any
+       data read before the error until it is exhausted, and only then return
+       the error */
+    if (state->err != Z_OK && state->err != Z_BUF_ERROR && !state->again &&
+            state->x.have == 0)
         return -1;
     if (state->again)
         gz_error(state, Z_OK, NULL);

     /* since an int is returned, make sure len fits in one, otherwise return
-       with an error (this avoids a flaw in the interface) */
+       with an error (this avoids a flaw in the interface) -- keep a pending
+       serious error, which is the one the caller needs to see */
     if ((int)len < 0) {
-        gz_error(state, Z_STREAM_ERROR, "request does not fit in an int");
+        if (state->err == Z_OK || state->err == Z_BUF_ERROR)
+            gz_error(state, Z_STREAM_ERROR, "request does not fit in an int");
         return -1;
     }

@@ -462,16 +472,21 @@ z_size_t ZEXPORT gzfread(voidp buf, z_size_t size, z_size_t nitems,
     if (state->mode != GZ_READ)
         return 0;

-    /* check that there was no (serious) error */
-    if (state->err != Z_OK && state->err != Z_BUF_ERROR && !state->again)
+    /* check that there was no (serious) error -- if there was, deliver any
+       data read before the error until it is exhausted, and only then return
+       the error */
+    if (state->err != Z_OK && state->err != Z_BUF_ERROR && !state->again &&
+            state->x.have == 0)
         return 0;
     if (state->again)
         gz_error(state, Z_OK, NULL);

-    /* compute bytes to read -- error on overflow */
+    /* compute bytes to read -- error on overflow, keeping a pending serious
+       error, which is the one the caller needs to see */
     len = nitems * size;
     if (size && len / size != nitems) {
-        gz_error(state, Z_STREAM_ERROR, "request does not fit in a size_t");
+        if (state->err == Z_OK || state->err == Z_BUF_ERROR)
+            gz_error(state, Z_STREAM_ERROR, "request does not fit in a size_t");
         return 0;
     }

@@ -496,8 +511,11 @@ int ZEXPORT gzgetc(gzFile file) {
     if (state->mode != GZ_READ)
         return -1;

-    /* check that there was no (serious) error */
-    if (state->err != Z_OK && state->err != Z_BUF_ERROR && !state->again)
+    /* check that there was no (serious) error -- if there was, deliver any
+       data read before the error until it is exhausted, and only then return
+       the error, as the gzgetc() macro does */
+    if (state->err != Z_OK && state->err != Z_BUF_ERROR && !state->again &&
+            state->x.have == 0)
         return -1;
     if (state->again)
         gz_error(state, Z_OK, NULL);
@@ -532,8 +550,12 @@ int ZEXPORT gzungetc(int c, gzFile file) {
     if (state->how == LOOK && state->x.have == 0)
         (void)gz_look(state);

-    /* check that there was no (serious) error */
-    if (state->err != Z_OK && state->err != Z_BUF_ERROR && !state->again)
+    /* check that there was no (serious) error -- a character can still be
+       pushed back after one, since it is data that was read before the
+       error, and it will be returned before the error is signaled -- but only
+       if there is an output buffer to put it in */
+    if (state->err != Z_OK && state->err != Z_BUF_ERROR && !state->again &&
+            state->size == 0)
         return -1;
     if (state->again)
         gz_error(state, Z_OK, NULL);
@@ -558,7 +580,8 @@ int ZEXPORT gzungetc(int c, gzFile file) {

     /* if no room, give up (must have already done a gzungetc()) */
     if (state->x.have == (state->size << 1)) {
-        gz_error(state, Z_DATA_ERROR, "out of room to push characters");
+        if (state->err == Z_OK || state->err == Z_BUF_ERROR)
+            gz_error(state, Z_DATA_ERROR, "out of room to push characters");
         return -1;
     }

@@ -594,8 +617,11 @@ char * ZEXPORT gzgets(gzFile file, char *buf, int len) {
     if (state->mode != GZ_READ)
         return NULL;

-    /* check that there was no (serious) error */
-    if (state->err != Z_OK && state->err != Z_BUF_ERROR && !state->again)
+    /* check that there was no (serious) error -- if there was, deliver any
+       data read before the error until it is exhausted, and only then return
+       the error */
+    if (state->err != Z_OK && state->err != Z_BUF_ERROR && !state->again &&
+            state->x.have == 0)
         return NULL;
     if (state->again)
         gz_error(state, Z_OK, NULL);
@@ -610,8 +636,9 @@ char * ZEXPORT gzgets(gzFile file, char *buf, int len) {
     left = (unsigned)len - 1;
     if (left) do {
         /* assure that something is in the output buffer */
-        if (state->x.have == 0 && gz_fetch(state) == -1)
-            break;                      /* error */
+        if (state->x.have == 0 && gz_fetch(state) == -1 &&
+                state->x.have == 0)
+            break;                      /* error, and nothing left to return */
         if (state->x.have == 0) {       /* end of file */
             state->past = 1;            /* read past end */
             break;                      /* return what we have */
diff --git a/zlib.h b/zlib.h
index ff137fc..2cbdeca 100644
--- a/zlib.h
+++ b/zlib.h
@@ -1501,9 +1501,12 @@ ZEXTERN z_size_t ZEXPORT gzfread(voidp buf, z_size_t size, z_size_t nitems,
      gzfread() returns the number of full items read of size size, or zero if
    the end of the file was reached and a full item could not be read, or if
    there was an error.  gzerror() must be consulted if zero is returned in
-   order to determine if there was an error.  If the multiplication of size and
-   nitems overflows, i.e. the product does not fit in a z_size_t, then nothing
-   is read, zero is returned, and the error state is set to Z_STREAM_ERROR.
+   order to determine if there was an error.  If some data was read before an
+   error, then that data is returned until exhausted, after which the next call
+   will return zero and gzerror() will report the error.  If the multiplication
+   of size and nitems overflows, i.e. the product does not fit in a z_size_t,
+   then nothing is read, zero is returned, and the error state is set to
+   Z_STREAM_ERROR.

      In the event that the end of file is reached and only a partial item is
    available at the end, i.e. the remaining uncompressed data length is not a
@@ -1780,7 +1783,8 @@ ZEXTERN const char * ZEXPORT gzerror(gzFile file, int *errnum);
    If errnum is not NULL, *errnum is set to zlib error number.  If an error
    occurred in the file system and not in the compression library, *errnum is
    set to Z_ERRNO and the application may consult errno to get the exact error
-   code.
+   code.  The error is reported by gzerror() as soon as it is detected, which
+   may be before all of the data read prior to the error has been returned.

      The application must not modify the returned string.  Future calls to
    this function may invalidate the previously returned string.  If file is