Windows: Fix filepath basename issue

On Windows, the cli_basename function should treat both '/' and '\' as path
separators. Most Windows APIs also accept both.

On Linux/Unix, it makes sense when using a filepath that is more for
informational purposes or where it may have come from a Windows system,
to treat the '\' as a path separator.
But in situations where the the path is needed for some critical action,
like moving or deleting a file, we can't treat it as a path separator.
This commit is contained in:
Valerie Snyder 2025-08-11 18:02:09 -04:00 committed by Valerie Snyder
parent 0cc5d75093
commit 18854bf4bc
No known key found for this signature in database
GPG key ID: DCBE519BFAF4C517
7 changed files with 42 additions and 21 deletions

View file

@ -452,7 +452,7 @@ static int traverse_rename(const char *source, const char *destination)
#endif
#ifndef _WIN32
ret = cli_basename(source, strlen(source), &source_basename);
ret = cli_basename(source, strlen(source), &source_basename, false /* posix_support_backslash_pathsep */);
if (CL_SUCCESS != ret) {
logg(LOGG_INFO, "traverse_rename: Failed to get basename of source path:%s\n\tError: %d\n", source, (int)ret);
goto done;
@ -564,7 +564,7 @@ static int traverse_unlink(const char *target)
goto done;
}
ret = cli_basename(target, strlen(target), &target_basename);
ret = cli_basename(target, strlen(target), &target_basename, false /* posix_support_backslash_pathsep */);
if (CL_SUCCESS != ret) {
logg(LOGG_INFO, "traverse_unlink: Failed to get basename of target path: %s\n\tError: %d\n", target, (int)ret);
goto done;

View file

@ -995,7 +995,7 @@ cl_error_t fmap_dump_to_file(fmap_t *map, const char *filepath, const char *tmpd
/* Create a filename prefix that includes the original filename, if available */
if (filepath != NULL) {
if (CL_SUCCESS != cli_basename(filepath, strlen(filepath), &filebase)) {
if (CL_SUCCESS != cli_basename(filepath, strlen(filepath), &filebase, true /* posix_support_backslash_pathsep */)) {
cli_dbgmsg("fmap_dump_to_file: Unable to determine basename from filepath.\n");
} else if ((start_offset != 0) && (end_offset != map->real_len)) {
/* If we're only dumping a portion of the file, include the offsets in the prefix,...

View file

@ -389,7 +389,7 @@ static cl_error_t cli_scanrar_file(const char *filepath, int desc, cli_ctx *ctx)
* Extract the file...
*/
if (0 != metadata.filename[0]) {
(void)cli_basename(metadata.filename, strlen(metadata.filename), &filename_base);
(void)cli_basename(metadata.filename, strlen(metadata.filename), &filename_base, true /* posix_support_backslash_pathsep */);
}
if (!(ctx->engine->keeptmp) ||
@ -837,7 +837,7 @@ static cl_error_t cli_scanegg(cli_ctx *ctx)
* Drop to a temp file, if requested.
*/
if (NULL != metadata.filename) {
(void)cli_basename(metadata.filename, strlen(metadata.filename), &filename_base);
(void)cli_basename(metadata.filename, strlen(metadata.filename), &filename_base, true /* posix_support_backslash_pathsep */);
}
if (ctx->engine->keeptmp) {
@ -4716,7 +4716,7 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type)
* Keep-temp enabled, so create a sub-directory to provide extraction directory recursion.
*/
if ((NULL != ctx->fmap->name) &&
(CL_SUCCESS == cli_basename(ctx->fmap->name, strlen(ctx->fmap->name), &fmap_basename))) {
(CL_SUCCESS == cli_basename(ctx->fmap->name, strlen(ctx->fmap->name), &fmap_basename, true /* posix_support_backslash_pathsep */))) {
/*
* The fmap has a name, lets include it in the new sub-directory.
*/
@ -5910,7 +5910,7 @@ static cl_error_t scan_common(cl_fmap_t *map, const char *filepath, const char *
if ((ctx.engine->keeptmp) &&
(NULL != ctx.target_filepath) &&
(CL_SUCCESS == cli_basename(ctx.target_filepath, strlen(ctx.target_filepath), &target_basename))) {
(CL_SUCCESS == cli_basename(ctx.target_filepath, strlen(ctx.target_filepath), &target_basename, true /* posix_support_backslash_pathsep */))) {
/* Include the basename in the temp directory */
new_temp_prefix_len = strlen("YYYYMMDD_HHMMSS-") + strlen(target_basename);
new_temp_prefix = cli_max_calloc(1, new_temp_prefix_len + 1);
@ -6186,7 +6186,7 @@ cl_error_t cl_scandesc_callback(int desc, const char *filename, const char **vir
}
if (NULL != filename) {
(void)cli_basename(filename, strlen(filename), &filename_base);
(void)cli_basename(filename, strlen(filename), &filename_base, true /* posix_support_backslash_pathsep */);
}
if (NULL == (map = fmap(desc, 0, sb.st_size, filename_base))) {
@ -6226,7 +6226,7 @@ cl_error_t cl_scanmap_callback(cl_fmap_t *map, const char *filename, const char
if (NULL != filename && map->name == NULL) {
// Use the provided name for the fmap name if one wasn't already set.
cli_basename(filename, strlen(filename), &map->name);
cli_basename(filename, strlen(filename), &map->name, true /* posix_support_backslash_pathsep */);
}
return scan_common(map, filename, virname, scanned, engine, scanoptions, context);

View file

@ -424,8 +424,7 @@ char *cli_strrcpy(char *dest, const char *source) /* by NJH */
return NULL;
}
while ((*dest++ = *source++))
;
while ((*dest++ = *source++));
return --dest;
}
@ -470,8 +469,7 @@ char *__cli_strndup(const char *s, size_t n)
size_t __cli_strnlen(const char *s, size_t n)
{
size_t i = 0;
for (; (i < n) && s[i] != '\0'; ++i)
;
for (; (i < n) && s[i] != '\0'; ++i);
return i;
}
@ -1058,8 +1056,11 @@ int cli_hexnibbles(char *str, int len)
return 0;
}
cl_error_t cli_basename(const char *filepath, size_t filepath_len,
char **filebase)
cl_error_t cli_basename(
const char *filepath,
size_t filepath_len,
char **filebase,
bool posix_support_backslash_pathsep)
{
cl_error_t status = CL_EARG;
const char *index = NULL;
@ -1071,13 +1072,25 @@ cl_error_t cli_basename(const char *filepath, size_t filepath_len,
index = filepath + filepath_len - 1;
#ifdef _WIN32
while (index > filepath) {
if (index[0] == PATHSEP[0])
if ((index[0] == '/') || (index[0] == '\\'))
break;
index--;
}
if ((index != filepath) || (index[0] == PATHSEP[0]))
if ((index != filepath) || (index[0] == '/') || (index[0] == '\\')) {
index++;
}
#else
while (index > filepath) {
if ((index[0] == '/') || (index[0] == '\\' && posix_support_backslash_pathsep))
break;
index--;
}
if ((index != filepath) || (index[0] == '/') || (index[0] == '\\' && posix_support_backslash_pathsep)) {
index++;
}
#endif
if (0 == CLI_STRNLEN(index, filepath_len - (index - filepath))) {
cli_dbgmsg("cli_basename: Provided path does not include a file name.\n");

View file

@ -96,15 +96,23 @@ int cli_hexnibbles(char *str, int len);
size_t cli_strlcat(char *dst, const char *src, size_t sz); /* libclamav/strlcat.c */
/**
* @brief Get the file basename including extension from a file path.
* @brief Get the file basename including extension from a file path.
*
* Will treat both '/' and '\' as path separators.
*
* Caller is responsible for freeing filebase.
* An empty string will be returned if the caller inputs a directory with a trailing slash (no file).
*
* @param filepath The filepath in question.
* @param[out] filebase An allocated string containing the file basename.
* @param posix_support_backslash_pathsep Whether to treat backslashes as path separators on Linux/Unix systems.
*
* @return cl_error_t CL_SUCCESS, CL_EARG, CL_EFORMAT, or CL_EMEM
*/
cl_error_t cli_basename(const char *filepath, size_t filepath_len, char **filebase);
cl_error_t cli_basename(
const char *filepath,
size_t filepath_len,
char **filebase,
bool posix_support_backslash_pathsep);
#endif

View file

@ -658,7 +658,7 @@ static cl_error_t parse_local_file_header(
src = fmap_need_ptr_once(map, zip, name_size);
if (name_size && (NULL != src)) {
memcpy(name, zip, name_size);
if (CL_SUCCESS != cli_basename(name, name_size, &original_filename)) {
if (CL_SUCCESS != cli_basename(name, name_size, &original_filename, true /* posix_support_backslash_pathsep */)) {
original_filename = NULL;
}
}

View file

@ -2623,7 +2623,7 @@ fc_error_t updatedb(
logg(LOGG_DEBUG, "updatedb: Moving signature file %s to database directory\n", signfile);
// get the basename of the signfile
if (CL_SUCCESS != cli_basename(signfile, strlen(signfile), &newSignFilename)) {
if (CL_SUCCESS != cli_basename(signfile, strlen(signfile), &newSignFilename, false /* posix_support_backslash_pathsep */)) {
logg(LOGG_ERROR, "updatedb: Failed to get basename of '%s'\n", signfile);
goto done;
}