From 30668554721f689420e7025bc245f45c22509406 Mon Sep 17 00:00:00 2001 From: Natsune Mochizuki Date: Wed, 8 Feb 2023 22:44:20 +0900 Subject: [PATCH] mingw: support for trailing spaces on Windows Since d2c84da (refuse to access paths with trailing spaces or periods), git for Windows no longer handles paths containing trailing spaces and periods. However, files with trailing spaces or periods can still be handled correctly by the Win32 API. For example, pass the file relative path, such as `touch("abc ");`, the Win32 API will trim the trailing spaces and periods. But pass the file absolute path, such as `touch("\\\\?\\C:\\abc ");`, it is possible to treat the file correctly. This patch allows checkout of files containing trailing spaces or periods only if `core.protectNTFS` is disabled (`false`). However, this patch does not change the handling of directories. This is because the two paths will be merged or access will be refused even if the above method is used. Signed-off-by: Natsune Mochizuki --- compat/mingw.c | 181 ++++++++++++++++++++++++++++++++++++++++++++++--- compat/mingw.h | 12 ++++ 2 files changed, 183 insertions(+), 10 deletions(-) diff --git a/compat/mingw.c b/compat/mingw.c index e433740381be9c..d4f200e8db67bf 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -292,11 +292,23 @@ static wchar_t *normalize_ntpath(wchar_t *wbuf) int mingw_unlink(const char *pathname) { - int ret, tries = 0; + int ret, tries = 0, wlen = 0; wchar_t wpathname[MAX_PATH]; - if (xutftowcs_path(wpathname, pathname) < 0) + + wlen = xutftowcs_path(wpathname, pathname); + if (wlen < 0) return -1; + if (!protect_ntfs && is_path_ends_with_special_character(pathname)) { + wchar_t abspath[MAX_PATH]; + + if (convert_to_abs_path_for_special_path(abspath, wpathname, + wlen) < 0) + return -1; + + wcscpy(wpathname, abspath); + } + if (DeleteFileW(wpathname)) return 0; @@ -457,6 +469,7 @@ int mingw_mkdir(const char *path, int mode) if (xutftowcs_path(wpath, path) < 0) return -1; + ret = _wmkdir(wpath); if (!ret && needs_hiding(path)) return set_hidden_flag(wpath, 1); @@ -543,6 +556,7 @@ int mingw_open (const char *filename, int oflags, ...) unsigned mode; int fd, create = (oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL); wchar_t wfilename[MAX_PATH]; + int path_len = -1; open_fn_t open_fn; va_start(args, oflags); @@ -561,8 +575,22 @@ int mingw_open (const char *filename, int oflags, ...) if (filename && !strcmp(filename, "/dev/null")) wcscpy(wfilename, L"nul"); - else if (xutftowcs_path(wfilename, filename) < 0) - return -1; + else { + path_len = xutftowcs_path(wfilename, filename); + if (path_len < 0) + return -1; + } + + if (!protect_ntfs && is_path_ends_with_special_character(filename)) { + wchar_t abspath[MAX_PATH]; + + if (convert_to_abs_path_for_special_path(abspath, wfilename, + path_len) < 0) + return -1; + + wcscpy(wfilename, abspath); + } + fd = open_fn(wfilename, oflags, mode); @@ -618,6 +646,7 @@ int mingw_fgetc(FILE *stream) FILE *mingw_fopen (const char *filename, const char *otype) { int hide = needs_hiding(filename); + int wlen; FILE *file; wchar_t wfilename[MAX_PATH], wotype[4]; if (filename && !strcmp(filename, "/dev/null")) @@ -626,8 +655,21 @@ FILE *mingw_fopen (const char *filename, const char *otype) int create = otype && strchr(otype, 'w'); errno = create ? EINVAL : ENOENT; return NULL; - } else if (xutftowcs_path(wfilename, filename) < 0) - return NULL; + } else { + wlen = xutftowcs_path(wfilename, filename); + if (wlen < 0) + return NULL; + if (!protect_ntfs && is_path_ends_with_special_character(filename)) { + wchar_t abspath[MAX_PATH]; + + if (convert_to_abs_path_for_special_path( + abspath, wfilename, wlen) < 0) + return NULL; + + wcscpy(wfilename, abspath); + } + + } if (xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0) return NULL; @@ -647,16 +689,31 @@ FILE *mingw_fopen (const char *filename, const char *otype) FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream) { int hide = needs_hiding(filename); + int wlen; FILE *file; wchar_t wfilename[MAX_PATH], wotype[4]; + if (filename && !strcmp(filename, "/dev/null")) wcscpy(wfilename, L"nul"); else if (!is_valid_win32_path(filename, 1)) { int create = otype && strchr(otype, 'w'); errno = create ? EINVAL : ENOENT; return NULL; - } else if (xutftowcs_path(wfilename, filename) < 0) - return NULL; + } else { + wlen = xutftowcs_path(wfilename, filename); + if (wlen < 0) + return NULL; + + if (!protect_ntfs && is_path_ends_with_special_character(filename)) { + wchar_t abspath[MAX_PATH]; + + if (convert_to_abs_path_for_special_path( + abspath, wfilename, wlen) < 0) + return NULL; + + wcscpy(wfilename, abspath); + } + } if (xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0) return NULL; @@ -721,12 +778,25 @@ int mingw_access(const char *filename, int mode) return _waccess(wfilename, mode & ~X_OK); } +/* cached length of current directory for convert_to_abs_path_for_special_path */ +static int current_directory_len = 0; + +/* cached path of current directory for convert_to_abs_path_for_special_path */ +static wchar_t *current_directory_path; + + int mingw_chdir(const char *dirname) { + int result; wchar_t wdirname[MAX_PATH]; if (xutftowcs_path(wdirname, dirname) < 0) return -1; - return _wchdir(wdirname); + + result =_wchdir(wdirname); + current_directory_len = GetCurrentDirectoryW(0, NULL); + ALLOC_ARRAY(current_directory_path, current_directory_len); + GetCurrentDirectoryW(current_directory_len, current_directory_path); + return result; } int mingw_chmod(const char *filename, int mode) @@ -799,9 +869,21 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf) { WIN32_FILE_ATTRIBUTE_DATA fdata; wchar_t wfilename[MAX_PATH]; - if (xutftowcs_path(wfilename, file_name) < 0) + int wlen = xutftowcs_path(wfilename, file_name); + if (wlen < 0) return -1; + if (is_path_ends_with_special_character(file_name)) { + wchar_t abspath[MAX_PATH]; + + int len = convert_to_abs_path_for_special_path(abspath, + wfilename, wlen); + if (len < 0) + return -1; + + wcscpy(wfilename, abspath); + } + if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) { buf->st_ino = 0; buf->st_gid = 0; @@ -2909,6 +2991,79 @@ int is_valid_win32_path(const char *path, int allow_literal_nul) } } +int is_path_ends_with_special_character(const char *path) +{ + int preceding_space_or_period = 0, i = 0, periods = 0; + + skip_dos_drive_prefix((char **)&path); + + for (;;) { + char c = *(path++); + + switch (c) { + case '\0': + case '/': + case '\\': + if (preceding_space_or_period && + (i != periods || periods > 2)) + return 1; + + if (!c) + return 0; + + i = periods = preceding_space_or_period = 0; + continue; + + case '.': + periods++; + /* fallthru */ + + case ' ': + preceding_space_or_period = 1; + i++; + continue; + } + + preceding_space_or_period = 0; + i++; + } +} + +int convert_to_abs_path_for_special_path(wchar_t *abspath, wchar_t *path, + int len) +{ + int result; + + /** + * if provided path is already absolute path, do not convert path again. + */ + if ((is_dir_sep(path[0])) || (!is_dir_sep(path[0]) && path[1] == ':')) { + wcscpy(abspath, path); + return len; + } + + /* convert to absolute path **without** using GetFullPathNameW because + * GetFullPathNameW returns invalid path */ + result = current_directory_len + len; + if (result >= MAX_PATH - 4) { + errno = ENAMETOOLONG; + return -1; + } + + wcscpy(abspath, L"\\\\?\\"); + wcscpy(abspath + 4, current_directory_path); + wcscpy(abspath + 4 + current_directory_len - 1, L"\\"); + wcscpy(abspath + 4 + current_directory_len, path); + + /* normalize slashes to backslashes because Windows does not recognize + * special paths as slashes */ + for (; *abspath; abspath++) + if (*abspath == '/') + *abspath = '\\'; + + return result + 4; +} + #if !defined(_MSC_VER) /* * Disable MSVCRT command line wildcard expansion (__getmainargs called from @@ -3066,6 +3221,12 @@ int wmain(int argc, const wchar_t **wargv) /* initialize Unicode console */ winansi_init(); + /* init path and length of current directory for convert_to_abs_path_for_special_path */ + current_directory_len = GetCurrentDirectoryW(0, NULL); + ALLOC_ARRAY(current_directory_path, current_directory_len); + GetCurrentDirectoryW(current_directory_len, current_directory_path); + + /* invoke the real main() using our utf8 version of argv. */ exit_status = main(argc, argv); diff --git a/compat/mingw.h b/compat/mingw.h index 209cf7cebadd17..6efd6a0788df28 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -487,6 +487,18 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report); int is_valid_win32_path(const char *path, int allow_literal_nul); #define is_valid_path(path) is_valid_win32_path(path, 0) +/** + * Verifies that the given path is a ends with special characters (`.` or ` `). + * + * Returns 1 upon ends with special characters, otherwise 0. + */ +int is_path_ends_with_special_character(const char *path); + +/** + * Convert to absolute path for special path. + */ +int convert_to_abs_path_for_special_path(wchar_t *abspath, wchar_t *path, int len); + /** * Converts UTF-8 encoded string to UTF-16LE. *