From 73b65e94f3531179de45c6f3c836a610e3d0a846 Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
Date: Thu, 23 Nov 2023 08:23:17 +0100
Subject: [PATCH] fopen: create short(er) temporary file name

Only using random letters in the name plus a ".tmp" extension. Not by
appending characters to the final file name.

Reported-by: Maksymilian Arciemowicz

Closes #12388

Conflict:Curl_rand_alnum -> Curl_rand_hex
Reference:https://github.com/curl/curl/commit/73b65e94f3531179de45c6f3c836a610e3d0a846
---
 lib/fopen.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 60 insertions(+), 5 deletions(-)

diff --git a/lib/fopen.c b/lib/fopen.c
index 75b8a7aa5..a73ac068e 100644
--- a/lib/fopen.c
+++ b/lib/fopen.c
@@ -39,6 +39,51 @@
 #include "curl_memory.h"
 #include "memdebug.h"
 
+/*
+  The dirslash() function breaks a null-terminated pathname string into
+  directory and filename components then returns the directory component up
+  to, *AND INCLUDING*, a final '/'.  If there is no directory in the path,
+  this instead returns a "" string.
+
+  This function returns a pointer to malloc'ed memory.
+
+  The input path to this function is expected to have a file name part.
+*/
+
+#ifdef _WIN32
+#define PATHSEP "\\"
+#define IS_SEP(x) (((x) == '/') || ((x) == '\\'))
+#elif defined(MSDOS) || defined(__EMX__) || defined(OS2)
+#define PATHSEP "\\"
+#define IS_SEP(x) ((x) == '\\')
+#else
+#define PATHSEP "/"
+#define IS_SEP(x) ((x) == '/')
+#endif
+
+static char *dirslash(const char *path)
+{
+  size_t n;
+  struct dynbuf out;
+  DEBUGASSERT(path);
+  Curl_dyn_init(&out, CURL_MAX_INPUT_LENGTH);
+  n = strlen(path);
+  if(n) {
+    /* find the rightmost path separator, if any */
+    while(n && !IS_SEP(path[n-1]))
+      --n;
+    /* skip over all the path separators, if any */
+    while(n && IS_SEP(path[n-1]))
+      --n;
+  }
+  if(Curl_dyn_addn(&out, path, n))
+    return NULL;
+  /* if there was a directory, append a single trailing slash */
+  if(n && Curl_dyn_addn(&out, PATHSEP, 1))
+    return NULL;
+  return Curl_dyn_ptr(&out);
+}
+
 /*
  * Curl_fopen() opens a file for writing with a temp name, to be renamed
  * to the final name when completed. If there is an existing file using this
@@ -50,25 +95,34 @@ CURLcode Curl_fopen(struct Curl_easy *data, const char *filename,
                     FILE **fh, char **tempname)
 {
   CURLcode result = CURLE_WRITE_ERROR;
-  unsigned char randsuffix[9];
+  unsigned char randbuf[41];
   char *tempstore = NULL;
   struct_stat sb;
   int fd = -1;
+  char *dir;
   *tempname = NULL;
 
+  dir = dirslash(filename);
+  if(!dir)
+    goto fail;
+
   *fh = fopen(filename, FOPEN_WRITETEXT);
   if(!*fh)
     goto fail;
-  if(fstat(fileno(*fh), &sb) == -1 || !S_ISREG(sb.st_mode))
+  if(fstat(fileno(*fh), &sb) == -1 || !S_ISREG(sb.st_mode)) {
+    free(dir);
     return CURLE_OK;
+  }
   fclose(*fh);
   *fh = NULL;
 
-  result = Curl_rand_hex(data, randsuffix, sizeof(randsuffix));
+  result = Curl_rand_hex(data, randbuf, sizeof(randbuf));
   if(result)
     goto fail;
 
-  tempstore = aprintf("%s.%s.tmp", filename, randsuffix);
+  /* The temp file name should not end up too long for the target file
+     system */
+  tempstore = aprintf("%s%s.tmp", dir, randbuf);
   if(!tempstore) {
     result = CURLE_OUT_OF_MEMORY;
     goto fail;
@@ -95,6 +149,7 @@ CURLcode Curl_fopen(struct Curl_easy *data, const char *filename,
   if(!*fh)
     goto fail;
 
+  free(dir);
   *tempname = tempstore;
   return CURLE_OK;
 
@@ -105,7 +160,7 @@ fail:
   }
 
   free(tempstore);
-
+  free(dir);
   return result;
 }
 
-- 
2.33.0

