Commit fd54d99 for zlib

commit fd54d99ea36166fea3f5cf319c87b0aeb14b28c9
Author: Mark Adler <git@madler.net>
Date:   Tue Nov 3 15:01:47 2026 -0800

    Avoid path traversals in miniunz selectively by path components.

    miniunz's path traversal protection would treat ".." as a problem
    regardless of whether it was a path component or just in the
    middle of a name. It would also remove leading periods whether or
    not it was a traversal issue.

    Now the path name is handled component by component, so only real
    vulnerabilities are excised.

    If the result is an empty name, it is now skipped with a message
    instead of failing to open.

diff --git a/contrib/minizip/miniunz.c b/contrib/minizip/miniunz.c
index 33e9868..19724d1 100644
--- a/contrib/minizip/miniunz.c
+++ b/contrib/minizip/miniunz.c
@@ -184,33 +184,57 @@ static int makedir(const char *newdir) {
   return 1;
 }

-/* Return the part of name that can be created under the extraction directory:
-   skip to the last "..", then past any leading separators, dots, or (on
-   Windows) drive specifiers, so that the result is always a relative path. */
-static const char* safe_path(const char* name) {
-    if (name[0]!='\0')
+/* Copy to out a version of the path name that can only be created under the
+   extraction directory. Both '/' and '\\' are taken as separators. Empty and
+   "." path components are removed, and a ".." component is removed along with
+   the component before it, if any. On Windows, drive specifiers at the start
+   and components of only dots and spaces are also removed. The result is
+   always a relative path, and is no longer than name. out must have room for
+   strlen(name) + 1 characters. */
+static void safe_path(char* out, const char* name) {
+    char* start = out;
+    while (*name != '\0')
     {
-        const char* relative_check = name;
-        while (relative_check[1]!='\0')
-        {
-            if (relative_check[0]=='.' && relative_check[1]=='.')
-                name = relative_check;
-            relative_check++;
-        }
-    }
-
-    for (;;)
-    {
-        if (name[0]=='/' || name[0]=='\\' || name[0]=='.')
+        const char* comp = name;
+        size_t len;
+        while (*name != '\0' && *name != '/' && *name != '\\')
             name++;
+        len = (size_t)(name - comp);
 #ifdef _WIN32
-        else if (name[0]!='\0' && name[1]==':')
-            name += 2;
+        while (out == start && len >= 2 && comp[1] == ':')
+        {
+            comp += 2;          /* remove drive specifiers, e.g. "C:" */
+            len -= 2;
+        }
+        {
+            /* Windows drops trailing dots and spaces from a name, so remove
+               any other component made of only those, such as "..." */
+            size_t i = 0;
+            while (i < len && (comp[i] == '.' || comp[i] == ' '))
+                i++;
+            if (i == len && !(len == 2 && comp[0] == '.' && comp[1] == '.'))
+                len = 0;
+        }
 #endif
-        else
-            break;
+        if (len == 2 && comp[0] == '.' && comp[1] == '.')
+        {
+            /* remove the previous component and its separator, if any */
+            if (out > start)
+                out--;
+            while (out > start && out[-1] != '/' && out[-1] != '\\')
+                out--;
+        }
+        else if (len != 0 && !(len == 1 && comp[0] == '.'))
+        {
+            memcpy(out, comp, len);
+            out += len;
+            if (*name != '\0')
+                *out++ = *name;     /* keep the separator */
+        }
+        if (*name != '\0')
+            name++;
     }
-    return name;
+    *out = '\0';
 }

 static void do_banner(void) {
@@ -334,6 +358,7 @@ static int do_list(unzFile uf) {

 static int do_extract_currentfile(unzFile uf, const int* popt_extract_without_path, int* popt_overwrite, const char* password) {
     char filename_inzip[65536+1];
+    char write_name[65536+1];
     char* filename_withoutpath;
     char* p;
     int err=UNZ_OK;
@@ -368,23 +393,27 @@ static int do_extract_currentfile(unzFile uf, const int* popt_extract_without_pa

     if ((*filename_withoutpath)=='\0')
     {
-        const char* write_dirname = safe_path(filename_inzip);
-        if (((*popt_extract_without_path)==0) && (write_dirname[0]!='\0'))
+        safe_path(write_name, filename_inzip);
+        if (((*popt_extract_without_path)==0) && (write_name[0]!='\0'))
         {
-            printf("creating directory: %s\n",write_dirname);
-            mymkdir(write_dirname);
+            printf("creating directory: %s\n",write_name);
+            mymkdir(write_name);
         }
     }
     else
     {
-        const char* write_filename;
+        const char* write_filename = write_name;
         int skip=0;

         if ((*popt_extract_without_path)==0)
-            write_filename = filename_inzip;
+            safe_path(write_name, filename_inzip);
         else
-            write_filename = filename_withoutpath;
-        write_filename = safe_path(write_filename);
+            safe_path(write_name, filename_withoutpath);
+        if (write_name[0]=='\0')
+        {
+            printf("skipping %s: no name left to extract to\n",filename_inzip);
+            skip = 1;
+        }

         err = unzOpenCurrentFilePassword(uf,password);
         if (err!=UNZ_OK)
@@ -429,14 +458,22 @@ static int do_extract_currentfile(unzFile uf, const int* popt_extract_without_pa
         {
             fout=FOPEN_FUNC(write_filename,"wb");
             /* some zipfile don't contain directory alone before file */
-            if ((fout==NULL) && ((*popt_extract_without_path)==0) &&
-                                (filename_withoutpath!=(char*)filename_inzip))
+            if ((fout==NULL) && ((*popt_extract_without_path)==0))
             {
-                char c=*(filename_withoutpath-1);
-                *(filename_withoutpath-1)='\0';
-                makedir(write_filename);
-                *(filename_withoutpath-1)=c;
-                fout=FOPEN_FUNC(write_filename,"wb");
+                /* make the directories leading up to the file */
+                char* sep = NULL;
+                char* q;
+                for (q = write_name; *q != '\0'; q++)
+                    if (*q == '/' || *q == '\\')
+                        sep = q;
+                if (sep != NULL)
+                {
+                    char c = *sep;
+                    *sep = '\0';
+                    makedir(write_name);
+                    *sep = c;
+                    fout=FOPEN_FUNC(write_filename,"wb");
+                }
             }

             if (fout==NULL)