Commit 370ef1b for zlib

commit 370ef1b41fd35fbfcf2af317ef3d8065a132f27a
Author: Mark Adler <git@madler.net>
Date:   Sun May 25 21:46:47 2025 -0700

    Return all available uncompressed data on error in gzread.c.

    The error is recorded, and will be detected by the application
    after all of the uncompressed data has been consumed and then one
    more call is made to read data. The error is available immediately
    from gzerror() if the application would like to know earlier.

diff --git a/gzlib.c b/gzlib.c
index 79a7e97..1c5c7be 100644
--- a/gzlib.c
+++ b/gzlib.c
@@ -104,6 +104,7 @@ local gzFile gz_open(const void *path, int fd, const char *mode) {
         return NULL;
     state->size = 0;            /* no buffers allocated yet */
     state->want = GZBUFSIZE;    /* requested buffer size */
+    state->err = Z_OK;          /* no error yet */
     state->msg = NULL;          /* no error message yet */

     /* interpret mode */
diff --git a/gzread.c b/gzread.c
index b9075da..2c70054 100644
--- a/gzread.c
+++ b/gzread.c
@@ -166,8 +166,10 @@ local int gz_decomp(gz_statep state) {
     had = strm->avail_out;
     do {
         /* get more input for inflate() */
-        if (strm->avail_in == 0 && gz_avail(state) == -1)
-            return -1;
+        if (strm->avail_in == 0 && gz_avail(state) == -1) {
+            ret = state->err;
+            break;
+        }
         if (strm->avail_in == 0) {
             gz_error(state, Z_BUF_ERROR, "unexpected end of file");
             break;
@@ -181,22 +183,23 @@ local int gz_decomp(gz_statep state) {
         if (ret == Z_STREAM_ERROR || ret == Z_NEED_DICT) {
             gz_error(state, Z_STREAM_ERROR,
                      "internal error: inflate stream corrupt");
-            return -1;
+            break;
         }
         if (ret == Z_MEM_ERROR) {
             gz_error(state, Z_MEM_ERROR, "out of memory");
-            return -1;
+            break;
         }
         if (ret == Z_DATA_ERROR) {              /* deflate stream invalid */
             if (state->junk == 1) {             /* trailing garbage is ok */
                 strm->avail_in = 0;
                 state->eof = 1;
                 state->how = LOOK;
+                ret = Z_OK;
                 break;
             }
             gz_error(state, Z_DATA_ERROR,
                      strm->msg == NULL ? "compressed data error" : strm->msg);
-            return -1;
+            break;
         }
     } while (strm->avail_out && ret != Z_STREAM_END);

@@ -208,10 +211,11 @@ local int gz_decomp(gz_statep state) {
     if (ret == Z_STREAM_END) {
         state->junk = 0;
         state->how = LOOK;
+        return 0;
     }

-    /* good decompression */
-    return 0;
+    /* return decompression status */
+    return ret != Z_OK ? -1 : 0;
 }

 /* Fetch data and put it in the output buffer.  Assumes state->x.have is 0.
@@ -280,12 +284,15 @@ local int gz_skip(gz_statep state) {
 }

 /* Read len bytes into buf from file, or less than len up to the end of the
-   input.  Return the number of bytes read.  If zero is returned, either the
-   end of file was reached, or there was an error.  state->err must be
-   consulted in that case to determine which. */
+   input. Return the number of bytes read. If zero is returned, either the end
+   of file was reached, or there was an error. state->err must be consulted in
+   that case to determine which. If there was an error, but some uncompressed
+   bytes were read before the error, then that count is returned. The error is
+   still recorded, and so is deferred until the next call. */
 local z_size_t gz_read(gz_statep state, voidp buf, z_size_t len) {
     z_size_t got;
     unsigned n;
+    int err;

     /* if len is zero, avoid unnecessary operations */
     if (len == 0)
@@ -297,6 +304,7 @@ local z_size_t gz_read(gz_statep state, voidp buf, z_size_t len) {

     /* get len bytes to buf, or less than len if at the end */
     got = 0;
+    err = 0;
     do {
         /* set n to the maximum amount of len that fits in an unsigned int */
         n = (unsigned)-1;
@@ -310,37 +318,36 @@ local z_size_t gz_read(gz_statep state, voidp buf, z_size_t len) {
             memcpy(buf, state->x.next, n);
             state->x.next += n;
             state->x.have -= n;
+            if (state->err != Z_OK)
+                /* caught deferred error from gz_fetch() */
+                err = -1;
         }

         /* output buffer empty -- return if we're at the end of the input */
-        else if (state->eof && state->strm.avail_in == 0) {
-            state->past = 1;        /* tried to read past end */
+        else if (state->eof && state->strm.avail_in == 0)
             break;
-        }

         /* need output data -- for small len or new stream load up our output
-           buffer */
+           buffer, so that gzgetc() can be fast */
         else if (state->how == LOOK || n < (state->size << 1)) {
             /* get more output, looking for header if required */
-            if (gz_fetch(state) == -1)
-                return 0;
+            if (gz_fetch(state) == -1 && state->x.have == 0)
+                /* if state->x.have != 0, error will be caught after copy */
+                err = -1;
             continue;       /* no progress yet -- go back to copy above */
             /* the copy above assures that we will leave with space in the
                output buffer, allowing at least one gzungetc() to succeed */
         }

         /* large len -- read directly into user buffer */
-        else if (state->how == COPY) {      /* read directly */
-            if (gz_load(state, (unsigned char *)buf, n, &n) == -1)
-                return 0;
-        }
+        else if (state->how == COPY)        /* read directly */
+            err = gz_load(state, (unsigned char *)buf, n, &n);

         /* large len -- decompress directly into user buffer */
         else {  /* state->how == GZIP */
             state->strm.avail_out = n;
             state->strm.next_out = (unsigned char *)buf;
-            if (gz_decomp(state) == -1)
-                return 0;
+            err = gz_decomp(state);
             n = state->x.have;
             state->x.have = 0;
         }
@@ -350,7 +357,11 @@ local z_size_t gz_read(gz_statep state, voidp buf, z_size_t len) {
         buf = (char *)buf + n;
         got += n;
         state->x.pos += n;
-    } while (len);
+    } while (len && !err);
+
+    /* note read past eof */
+    if (len && state->eof)
+        state->past = 1;

     /* return number of bytes read into user buffer */
     return got;
@@ -537,7 +548,7 @@ char * ZEXPORT gzgets(gzFile file, char *buf, int len) {
     if (left) do {
         /* assure that something is in the output buffer */
         if (state->x.have == 0 && gz_fetch(state) == -1)
-            return NULL;                /* error */
+            break;                      /* error */
         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 9ec7bc1..fa8b3d4 100644
--- a/zlib.h
+++ b/zlib.h
@@ -1454,7 +1454,8 @@ ZEXTERN int ZEXPORT gzread(gzFile file, voidp buf, unsigned len);
      gzread returns the number of uncompressed bytes actually read, less than
    len for end of file, or -1 for error.  If len is too large to fit in an int,
    then nothing is read, -1 is returned, and the error state is set to
-   Z_STREAM_ERROR.
+   Z_STREAM_ERROR. If some data was read before an error, then that data is
+   returned until exhausted, after which the next call will signal the error.
 */

 ZEXTERN z_size_t ZEXPORT gzfread(voidp buf, z_size_t size, z_size_t nitems,
@@ -1536,8 +1537,9 @@ ZEXTERN char * ZEXPORT gzgets(gzFile file, char *buf, int len);
    left untouched.

      gzgets returns buf which is a null-terminated string, or it returns NULL
-   for end-of-file or in case of error.  If there was an error, the contents at
-   buf are indeterminate.
+   for end-of-file or in case of error. If some data was read before an error,
+   then that data is returned until exhausted, after which the next call will
+   return NULL to signal the error.
 */

 ZEXTERN int ZEXPORT gzputc(gzFile file, int c);
@@ -1548,11 +1550,14 @@ ZEXTERN int ZEXPORT gzputc(gzFile file, int c);

 ZEXTERN int ZEXPORT gzgetc(gzFile file);
 /*
-     Read and decompress one byte from file.  gzgetc returns this byte or -1
-   in case of end of file or error.  This is implemented as a macro for speed.
-   As such, it does not do all of the checking the other functions do.  I.e.
-   it does not check to see if file is NULL, nor whether the structure file
-   points to has been clobbered or not.
+      Read and decompress one byte from file. gzgetc returns this byte or -1 in
+   case of end of file or error. If some data was read before an error, then
+   that data is returned until exhausted, after which the next call will return
+   -1 to signal the error.
+
+      This is implemented as a macro for speed. As such, it does not do all of
+   the checking the other functions do. I.e. it does not check to see if file
+   is NULL, nor whether the structure file points to has been clobbered or not.
 */

 ZEXTERN int ZEXPORT gzungetc(int c, gzFile file);