curlx: add curlx_rename(), fix to support long filenames on Windows

Move existing `Curl_rename()` `rename()` wrapper from lib to
curlx/fopen, and make it a curlx macro/function. To allow using
the local worker function to fixup long filenames on Windows.

Then fix the Windows-specific rename implementation to support long
filenames. This operation may happen when using a cookie jar, HSTS cache
or alt-svc cache, via libcurl or the curl tool.

Before this patch, when passing a long filename to the above options,
a `<random>.tmp` file was left on the disk without renaming it to the
filename passed to curl. There was also 1 second delay for each
attempted rename operation.

Also:
- checksrc: ban raw `rename()` and `MoveFileEx*()` functions.
- Note: `Curl_rename()` returned 1 on failure before this patch, while
  `curlx_rename()` returns -1 after, to match POSIX `rename()`.

Refs:
https://learn.microsoft.com/windows/win32/api/winbase/nf-winbase-movefileexa
https://learn.microsoft.com/windows/win32/fileio/maximum-file-path-limitation

Ref: #20040

Closes #20042
This commit is contained in:
Viktor Szakats
2025-12-20 01:44:46 +01:00
parent 424cef6733
commit 6d0ee7b17b
10 changed files with 81 additions and 104 deletions

View File

@@ -375,12 +375,16 @@ This is the full list of functions generally banned.
localtime
malloc
mbstowcs
MoveFileEx
MoveFileExA
MoveFileExW
msnprintf
mvsnprintf
open
printf
realloc
recv
rename
send
snprintf
socket

View File

@@ -237,7 +237,6 @@ LIB_CFILES = \
psl.c \
rand.c \
ratelimit.c \
rename.c \
request.c \
rtsp.c \
select.c \
@@ -366,7 +365,6 @@ LIB_HFILES = \
psl.h \
rand.h \
ratelimit.h \
rename.h \
request.h \
rtsp.h \
select.h \

View File

@@ -35,7 +35,6 @@
#include "parsedate.h"
#include "sendf.h"
#include "curlx/warnless.h"
#include "rename.h"
#include "strdup.h"
#include "curlx/inet_pton.h"
#include "curlx/strparse.h"
@@ -379,7 +378,7 @@ CURLcode Curl_altsvc_save(struct Curl_easy *data,
break;
}
curlx_fclose(out);
if(!result && tempstore && Curl_rename(tempstore, file))
if(!result && tempstore && curlx_rename(tempstore, file))
result = CURLE_WRITE_ERROR;
if(result && tempstore)

View File

@@ -37,7 +37,6 @@
#include "curl_get_line.h"
#include "curl_memrchr.h"
#include "parsedate.h"
#include "rename.h"
#include "strdup.h"
#include "llist.h"
#include "bufref.h"
@@ -1534,7 +1533,7 @@ static CURLcode cookie_output(struct Curl_easy *data,
if(!use_stdout) {
curlx_fclose(out);
out = NULL;
if(tempstore && Curl_rename(tempstore, filename)) {
if(tempstore && curlx_rename(tempstore, filename)) {
error = CURLE_WRITE_ERROR;
goto error;
}

View File

@@ -44,6 +44,7 @@ int curlx_fseek(void *stream, curl_off_t offset, int whence)
#include <share.h> /* for _SH_DENYNO */
#include "multibyte.h"
#include "timeval.h"
#ifdef CURLDEBUG
/*
@@ -436,6 +437,72 @@ int curlx_win32_stat(const char *path, struct_stat *buffer)
return result;
}
#if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_COOKIES) || \
!defined(CURL_DISABLE_ALTSVC)
/* rename() on Windows does not overwrite, so we cannot use it here.
MoveFileEx() will overwrite and is usually atomic, however it fails
when there are open handles to the file. */
int curlx_win32_rename(const char *oldpath, const char *newpath)
{
int res = -1; /* fail */
#ifdef UNICODE
TCHAR *tchar_oldpath = curlx_convert_UTF8_to_wchar(oldpath);
TCHAR *tchar_newpath = curlx_convert_UTF8_to_wchar(newpath);
#else
const TCHAR *tchar_oldpath = oldpath;
const TCHAR *tchar_newpath = newpath;
#endif
if(tchar_oldpath && tchar_newpath) {
const int max_wait_ms = 1000;
struct curltime start;
TCHAR *oldpath_fixed = NULL;
TCHAR *newpath_fixed = NULL;
const TCHAR *target_oldpath;
const TCHAR *target_newpath;
if(fix_excessive_path(tchar_oldpath, &oldpath_fixed))
target_oldpath = oldpath_fixed;
else
target_oldpath = tchar_oldpath;
if(fix_excessive_path(tchar_newpath, &newpath_fixed))
target_newpath = newpath_fixed;
else
target_newpath = tchar_newpath;
start = curlx_now();
for(;;) {
timediff_t diff;
/* !checksrc! disable BANNEDFUNC 1 */
if(MoveFileEx(target_oldpath, target_newpath,
MOVEFILE_REPLACE_EXISTING)) {
res = 0; /* success */
break;
}
diff = curlx_timediff_ms(curlx_now(), start);
if(diff < 0 || diff > max_wait_ms) {
break;
}
Sleep(1);
}
CURLX_FREE(oldpath_fixed);
CURLX_FREE(newpath_fixed);
}
#ifdef UNICODE
curlx_free(tchar_oldpath);
curlx_free(tchar_newpath);
#endif
return res;
}
#endif
#undef CURLX_MALLOC
#undef CURLX_FREE

View File

@@ -48,15 +48,18 @@ FILE *curlx_win32_fopen(const char *filename, const char *mode);
FILE *curlx_win32_freopen(const char *filename, const char *mode, FILE *fh);
int curlx_win32_stat(const char *path, struct_stat *buffer);
int curlx_win32_open(const char *filename, int oflag, ...);
int curlx_win32_rename(const char *oldpath, const char *newpath);
#define CURLX_FOPEN_LOW(fname, mode) curlx_win32_fopen(fname, mode)
#define CURLX_FREOPEN_LOW(fname, mode, fh) curlx_win32_freopen(fname, mode, fh)
#define curlx_stat(fname, stp) curlx_win32_stat(fname, stp)
#define curlx_open curlx_win32_open
#define curlx_rename curlx_win32_rename
#else
#define CURLX_FOPEN_LOW fopen
#define CURLX_FREOPEN_LOW freopen
#define curlx_stat(fname, stp) stat(fname, stp)
#define curlx_open open
#define curlx_rename rename
#endif
#ifdef CURLDEBUG

View File

@@ -35,7 +35,6 @@
#include "curl_get_line.h"
#include "sendf.h"
#include "parsedate.h"
#include "rename.h"
#include "curl_share.h"
#include "strdup.h"
#include "curlx/strparse.h"
@@ -362,7 +361,7 @@ CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h,
break;
}
curlx_fclose(out);
if(!result && tempstore && Curl_rename(tempstore, file))
if(!result && tempstore && curlx_rename(tempstore, file))
result = CURLE_WRITE_ERROR;
if(result && tempstore)

View File

@@ -1,67 +0,0 @@
/***************************************************************************
* _ _ ____ _
* 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 "curl_setup.h"
#if (!defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_COOKIES)) || \
!defined(CURL_DISABLE_ALTSVC)
#include "curlx/multibyte.h"
#include "curlx/timeval.h"
#include "rename.h"
/* return 0 on success, 1 on error */
int Curl_rename(const char *oldpath, const char *newpath)
{
#ifdef _WIN32
/* rename() on Windows does not overwrite, so we cannot use it here.
MoveFileEx() will overwrite and is usually atomic, however it fails
when there are open handles to the file. */
const int max_wait_ms = 1000;
struct curltime start = curlx_now();
TCHAR *tchar_oldpath = curlx_convert_UTF8_to_tchar(oldpath);
TCHAR *tchar_newpath = curlx_convert_UTF8_to_tchar(newpath);
for(;;) {
timediff_t diff;
if(MoveFileEx(tchar_oldpath, tchar_newpath, MOVEFILE_REPLACE_EXISTING)) {
curlx_free(tchar_oldpath);
curlx_free(tchar_newpath);
break;
}
diff = curlx_timediff_ms(curlx_now(), start);
if(diff < 0 || diff > max_wait_ms) {
curlx_free(tchar_oldpath);
curlx_free(tchar_newpath);
return 1;
}
Sleep(1);
}
#else
if(rename(oldpath, newpath))
return 1;
#endif
return 0;
}
#endif

View File

@@ -1,29 +0,0 @@
#ifndef HEADER_CURL_RENAME_H
#define HEADER_CURL_RENAME_H
/***************************************************************************
* _ _ ____ _
* 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
*
***************************************************************************/
int Curl_rename(const char *oldpath, const char *newpath);
#endif /* HEADER_CURL_RENAME_H */

View File

@@ -92,12 +92,16 @@ my %banfunc = (
"localtime" => 1,
"malloc" => 1,
"mbstowcs" => 1,
"MoveFileEx" => 1,
"MoveFileExA" => 1,
"MoveFileExW" => 1,
"msnprintf" => 1,
"mvsnprintf" => 1,
"open" => 1,
"printf" => 1,
"realloc" => 1,
"recv" => 1,
"rename" => 1,
"send" => 1,
"snprintf" => 1,
"socket" => 1,