]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
tool_dirhie: fix to create drive-relative directory
authorViktor Szakats <commit@vsz.me>
Sun, 26 Apr 2026 11:38:47 +0000 (13:38 +0200)
committerViktor Szakats <commit@vsz.me>
Mon, 27 Apr 2026 10:09:09 +0000 (12:09 +0200)
Fix to create the top directory `foo` when specified as
`X:foo\bar\filename`, on Windows and MS-DOS. Add test to verify.

Caught by Codex Security

Follow-up to 787ee935acd5867bdac836b2043b6095eed2c29e #16566

Closes #21449

src/tool_dirhie.c
src/tool_dirhie.h
tests/data/Makefile.am
tests/data/test1720 [new file with mode: 0644]
tests/tunit/Makefile.inc
tests/tunit/tool1720.c [new file with mode: 0644]

index 894c63803407c3c17047cbf37f2effdf32e353e7..82377a718edc5f9f7bd3575d15a69e98aec96394 100644 (file)
@@ -26,7 +26,9 @@
 #include "tool_dirhie.h"
 #include "tool_msgs.h"
 
-#ifdef _WIN32
+#ifdef UNITTESTS
+#  define toolx_mkdir(x, y) create_dir_hierarchy_trace_mkdir(x)
+#elif defined(_WIN32)
 #  include <direct.h>
 #  define toolx_mkdir(x, y) _mkdir(x)
 #elif defined(MSDOS) && !defined(__DJGPP__)
 #  define toolx_mkdir mkdir
 #endif
 
+#ifdef UNITTESTS
+static struct dynbuf mkdir_results;
+
+UNITTEST struct dynbuf *create_dir_hierarchy_trace_dynres(void)
+{
+  return &mkdir_results;
+}
+
+static int create_dir_hierarchy_trace_mkdir(const char *dir)
+{
+  return !dir ||
+    curlx_dyn_add(&mkdir_results, dir) ||
+    curlx_dyn_add(&mkdir_results, "|") ? -1 : 0;
+}
+#endif
+
 static void show_dir_errno(const char *name)
 {
   switch(errno) {
@@ -105,12 +123,11 @@ CURLcode create_dir_hierarchy(const char *outfile)
 
 #if defined(_WIN32) || defined(MSDOS)
     if(!curlx_dyn_len(&dirbuf)) {
-      /* Skip creating a drive's current directory. It may seem as though that
-         would harmlessly fail but it could be a corner case if X: did not
-         exist, since we would be creating it erroneously. eg if outfile is
-         X:\foo\bar\filename then do not mkdir X: This logic takes into
+      /* Skip creating a standalone Windows/MS-DOS drive letter 'X:', e.g.
+         if outfile is X:\foo\bar\filename. Do create drive-relative
+         directories e.g. in outfile X:foo\bar\filename. This logic takes into
          account unsupported drives !:, 1:, etc. */
-      if(len > 1 && (outfile[1] == ':'))
+      if(len == 2 && (outfile[1] == ':'))
         skip = TRUE;
     }
 #endif
index d48615a0c0d16bb15e79f157a49e3e55bc5d026b..60b9470a57a820cc698cf7cce9e4373e7232e1fa 100644 (file)
  ***************************************************************************/
 #include "tool_setup.h"
 
+#ifdef UNITTESTS
+UNITTEST struct dynbuf *create_dir_hierarchy_trace_dynres(void);
+#endif
+
 CURLcode create_dir_hierarchy(const char *outfile);
 
 #endif /* HEADER_CURL_TOOL_DIRHIE_H */
index 00d3e4166ea495be3ad93893f3cdbce3b021d956..5a517df9f3c9b23bd3fc35304fb03735fbf2bb04 100644 (file)
@@ -230,6 +230,7 @@ test1680 test1681 test1682 test1683 test1684 test1685 \
 \
 test1700 test1701 test1702 test1703 test1704 test1705 test1706 test1707 \
 test1708 test1709 test1710 test1711 test1712 test1713 test1714 test1715 \
+test1720 \
 \
 test1800 test1801 test1802 test1847 test1848 test1849 test1850 test1851 \
 \
diff --git a/tests/data/test1720 b/tests/data/test1720
new file mode 100644 (file)
index 0000000..22edb19
--- /dev/null
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="US-ASCII"?>
+<testcase>
+<info>
+<keywords>
+unittest
+</keywords>
+</info>
+
+# Client-side
+<client>
+<features>
+unittest
+</features>
+<name>
+create_dir_hierarchy()
+</name>
+<tool>
+tool%TESTNUMBER
+</tool>
+</client>
+
+<verify>
+</verify>
+</testcase>
index 1f3c8cb9b7e4016a7e38f332f897fbedcad4a077..27a6163173d1152902df21aa8dcab7844d2f3e22 100644 (file)
@@ -34,4 +34,5 @@ TESTS_C = \
   tool1604.c \
   tool1621.c \
   tool1622.c \
-  tool1623.c
+  tool1623.c \
+  tool1720.c
diff --git a/tests/tunit/tool1720.c b/tests/tunit/tool1720.c
new file mode 100644 (file)
index 0000000..80e3c15
--- /dev/null
@@ -0,0 +1,79 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "unitcheck.h"
+#include "tool_dirhie.h"
+
+static CURLcode test_tool1720(const char *arg)
+{
+  UNITTEST_BEGIN_SIMPLE
+
+  static const char *check[] = {
+    "",
+    "(null)",
+    "filename",
+    "(null)",
+    "foo/bar/",
+    "foo|foo/bar|",
+    "foo/bar/filename",
+    "foo|foo/bar|",
+    "/foo/bar/filename",
+    "/foo|/foo/bar|",
+#if defined(_WIN32) || defined(MSDOS)
+    "C:/foo/bar/filename",
+    "C:/foo|C:/foo/bar|",
+    "C:foo/bar/filename",
+    "C:foo|C:foo/bar|",
+    "foo\\bar\\filename",
+    "foo|foo\\bar|",
+    "\\foo\\bar\\filename",
+    "\\foo|\\foo\\bar|",
+    "C:\\foo\\bar\\filename",
+    "C:\\foo|C:\\foo\\bar|",
+    "C:foo\\bar\\filename",
+    "C:foo|C:foo\\bar|",
+#endif
+  };
+
+  size_t i;
+  struct dynbuf *res = create_dir_hierarchy_trace_dynres();
+
+  curlx_dyn_init(res, 256);
+
+  for(i = 0; i < CURL_ARRAYSIZE(check); i += 2) {
+    const char *actual;
+    curlx_dyn_reset(res);
+    create_dir_hierarchy(check[i]);
+    actual = curlx_dyn_ptr(res);
+    if(!actual)
+      actual = "(null)";
+    if(strcmp(check[i + 1], actual)) {
+      curl_mprintf("Expected '%s' got '%s'\n", check[i + 1], actual);
+      unitfail++;
+    }
+  }
+
+  curlx_dyn_free(res);
+
+  UNITTEST_END_SIMPLE
+}