mirror of
https://github.com/git/git.git
synced 2024-10-28 21:07:52 +01:00
mingw: refuse paths containing reserved names
There are a couple of reserved names that cannot be file names on Windows, such as `AUX`, `NUL`, etc. For an almost complete list, see https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file If one would try to create a directory named `NUL`, it would actually "succeed", i.e. the call would return success, but nothing would be created. Worse, even adding a file extension to the reserved name does not make it a valid file name. To understand the rationale behind that behavior, see https://devblogs.microsoft.com/oldnewthing/20031022-00/?p=42073 Let's just disallow them all. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
98d9b23e90
commit
4dc42c6c18
3 changed files with 110 additions and 18 deletions
104
compat/mingw.c
104
compat/mingw.c
|
@ -393,7 +393,7 @@ int mingw_mkdir(const char *path, int mode)
|
||||||
int ret;
|
int ret;
|
||||||
wchar_t wpath[MAX_PATH];
|
wchar_t wpath[MAX_PATH];
|
||||||
|
|
||||||
if (!is_valid_win32_path(path)) {
|
if (!is_valid_win32_path(path, 0)) {
|
||||||
errno = EINVAL;
|
errno = EINVAL;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -479,7 +479,7 @@ int mingw_open (const char *filename, int oflags, ...)
|
||||||
mode = va_arg(args, int);
|
mode = va_arg(args, int);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
||||||
if (!is_valid_win32_path(filename)) {
|
if (!is_valid_win32_path(filename, !create)) {
|
||||||
errno = create ? EINVAL : ENOENT;
|
errno = create ? EINVAL : ENOENT;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -550,14 +550,13 @@ FILE *mingw_fopen (const char *filename, const char *otype)
|
||||||
int hide = needs_hiding(filename);
|
int hide = needs_hiding(filename);
|
||||||
FILE *file;
|
FILE *file;
|
||||||
wchar_t wfilename[MAX_PATH], wotype[4];
|
wchar_t wfilename[MAX_PATH], wotype[4];
|
||||||
if (!is_valid_win32_path(filename)) {
|
if (filename && !strcmp(filename, "/dev/null"))
|
||||||
|
wcscpy(wfilename, L"nul");
|
||||||
|
else if (!is_valid_win32_path(filename, 1)) {
|
||||||
int create = otype && strchr(otype, 'w');
|
int create = otype && strchr(otype, 'w');
|
||||||
errno = create ? EINVAL : ENOENT;
|
errno = create ? EINVAL : ENOENT;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
} else if (xutftowcs_path(wfilename, filename) < 0)
|
||||||
if (filename && !strcmp(filename, "/dev/null"))
|
|
||||||
wcscpy(wfilename, L"nul");
|
|
||||||
else if (xutftowcs_path(wfilename, filename) < 0)
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
|
if (xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
|
||||||
|
@ -580,14 +579,13 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
|
||||||
int hide = needs_hiding(filename);
|
int hide = needs_hiding(filename);
|
||||||
FILE *file;
|
FILE *file;
|
||||||
wchar_t wfilename[MAX_PATH], wotype[4];
|
wchar_t wfilename[MAX_PATH], wotype[4];
|
||||||
if (!is_valid_win32_path(filename)) {
|
if (filename && !strcmp(filename, "/dev/null"))
|
||||||
|
wcscpy(wfilename, L"nul");
|
||||||
|
else if (!is_valid_win32_path(filename, 1)) {
|
||||||
int create = otype && strchr(otype, 'w');
|
int create = otype && strchr(otype, 'w');
|
||||||
errno = create ? EINVAL : ENOENT;
|
errno = create ? EINVAL : ENOENT;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
} else if (xutftowcs_path(wfilename, filename) < 0)
|
||||||
if (filename && !strcmp(filename, "/dev/null"))
|
|
||||||
wcscpy(wfilename, L"nul");
|
|
||||||
else if (xutftowcs_path(wfilename, filename) < 0)
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
|
if (xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
|
||||||
|
@ -2412,14 +2410,16 @@ static void setup_windows_environment(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int is_valid_win32_path(const char *path)
|
int is_valid_win32_path(const char *path, int allow_literal_nul)
|
||||||
{
|
{
|
||||||
|
const char *p = path;
|
||||||
int preceding_space_or_period = 0, i = 0, periods = 0;
|
int preceding_space_or_period = 0, i = 0, periods = 0;
|
||||||
|
|
||||||
if (!protect_ntfs)
|
if (!protect_ntfs)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
skip_dos_drive_prefix((char **)&path);
|
skip_dos_drive_prefix((char **)&path);
|
||||||
|
goto segment_start;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
char c = *(path++);
|
char c = *(path++);
|
||||||
|
@ -2434,7 +2434,83 @@ int is_valid_win32_path(const char *path)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
i = periods = preceding_space_or_period = 0;
|
i = periods = preceding_space_or_period = 0;
|
||||||
continue;
|
|
||||||
|
segment_start:
|
||||||
|
switch (*path) {
|
||||||
|
case 'a': case 'A': /* AUX */
|
||||||
|
if (((c = path[++i]) != 'u' && c != 'U') ||
|
||||||
|
((c = path[++i]) != 'x' && c != 'X')) {
|
||||||
|
not_a_reserved_name:
|
||||||
|
path += i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'c': case 'C': /* COM<N>, CON, CONIN$, CONOUT$ */
|
||||||
|
if ((c = path[++i]) != 'o' && c != 'O')
|
||||||
|
goto not_a_reserved_name;
|
||||||
|
c = path[++i];
|
||||||
|
if (c == 'm' || c == 'M') { /* COM<N> */
|
||||||
|
if (!isdigit(path[++i]))
|
||||||
|
goto not_a_reserved_name;
|
||||||
|
} else if (c == 'n' || c == 'N') { /* CON */
|
||||||
|
c = path[i + 1];
|
||||||
|
if ((c == 'i' || c == 'I') &&
|
||||||
|
((c = path[i + 2]) == 'n' ||
|
||||||
|
c == 'N') &&
|
||||||
|
path[i + 3] == '$')
|
||||||
|
i += 3; /* CONIN$ */
|
||||||
|
else if ((c == 'o' || c == 'O') &&
|
||||||
|
((c = path[i + 2]) == 'u' ||
|
||||||
|
c == 'U') &&
|
||||||
|
((c = path[i + 3]) == 't' ||
|
||||||
|
c == 'T') &&
|
||||||
|
path[i + 4] == '$')
|
||||||
|
i += 4; /* CONOUT$ */
|
||||||
|
} else
|
||||||
|
goto not_a_reserved_name;
|
||||||
|
break;
|
||||||
|
case 'l': case 'L': /* LPT<N> */
|
||||||
|
if (((c = path[++i]) != 'p' && c != 'P') ||
|
||||||
|
((c = path[++i]) != 't' && c != 'T') ||
|
||||||
|
!isdigit(path[++i]))
|
||||||
|
goto not_a_reserved_name;
|
||||||
|
break;
|
||||||
|
case 'n': case 'N': /* NUL */
|
||||||
|
if (((c = path[++i]) != 'u' && c != 'U') ||
|
||||||
|
((c = path[++i]) != 'l' && c != 'L') ||
|
||||||
|
(allow_literal_nul &&
|
||||||
|
!path[i + 1] && p == path))
|
||||||
|
goto not_a_reserved_name;
|
||||||
|
break;
|
||||||
|
case 'p': case 'P': /* PRN */
|
||||||
|
if (((c = path[++i]) != 'r' && c != 'R') ||
|
||||||
|
((c = path[++i]) != 'n' && c != 'N'))
|
||||||
|
goto not_a_reserved_name;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* So far, this looks like a reserved name. Let's see
|
||||||
|
* whether it actually is one: trailing spaces, a file
|
||||||
|
* extension, or an NTFS Alternate Data Stream do not
|
||||||
|
* matter, the name is still reserved if any of those
|
||||||
|
* follow immediately after the actual name.
|
||||||
|
*/
|
||||||
|
i++;
|
||||||
|
if (path[i] == ' ') {
|
||||||
|
preceding_space_or_period = 1;
|
||||||
|
while (path[++i] == ' ')
|
||||||
|
; /* skip all spaces */
|
||||||
|
}
|
||||||
|
|
||||||
|
c = path[i];
|
||||||
|
if (c && c != '.' && c != ':' && c != '/' && c != '\\')
|
||||||
|
goto not_a_reserved_name;
|
||||||
|
|
||||||
|
/* contains reserved name */
|
||||||
|
return 0;
|
||||||
case '.':
|
case '.':
|
||||||
periods++;
|
periods++;
|
||||||
/* fallthru */
|
/* fallthru */
|
||||||
|
|
|
@ -461,10 +461,17 @@ char *mingw_query_user_email(void);
|
||||||
*
|
*
|
||||||
* - contain any of the reserved characters, e.g. `:`, `;`, `*`, etc
|
* - contain any of the reserved characters, e.g. `:`, `;`, `*`, etc
|
||||||
*
|
*
|
||||||
|
* - correspond to reserved names (such as `AUX`, `PRN`, etc)
|
||||||
|
*
|
||||||
|
* The `allow_literal_nul` parameter controls whether the path `NUL` should
|
||||||
|
* be considered valid (this makes sense e.g. before opening files, as it is
|
||||||
|
* perfectly legitimate to open `NUL` on Windows, just as it is to open
|
||||||
|
* `/dev/null` on Unix/Linux).
|
||||||
|
*
|
||||||
* Returns 1 upon success, otherwise 0.
|
* Returns 1 upon success, otherwise 0.
|
||||||
*/
|
*/
|
||||||
int is_valid_win32_path(const char *path);
|
int is_valid_win32_path(const char *path, int allow_literal_nul);
|
||||||
#define is_valid_path(path) is_valid_win32_path(path)
|
#define is_valid_path(path) is_valid_win32_path(path, 0)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts UTF-8 encoded string to UTF-16LE.
|
* Converts UTF-8 encoded string to UTF-16LE.
|
||||||
|
|
|
@ -465,11 +465,14 @@ test_expect_success 'match .gitmodules' '
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success MINGW 'is_valid_path() on Windows' '
|
test_expect_success MINGW 'is_valid_path() on Windows' '
|
||||||
test-tool path-utils is_valid_path \
|
test-tool path-utils is_valid_path \
|
||||||
win32 \
|
win32 \
|
||||||
"win32 x" \
|
"win32 x" \
|
||||||
../hello.txt \
|
../hello.txt \
|
||||||
C:\\git \
|
C:\\git \
|
||||||
|
comm \
|
||||||
|
conout.c \
|
||||||
|
lptN \
|
||||||
\
|
\
|
||||||
--not \
|
--not \
|
||||||
"win32 " \
|
"win32 " \
|
||||||
|
@ -477,7 +480,13 @@ test_expect_success MINGW 'is_valid_path() on Windows' '
|
||||||
"win32." \
|
"win32." \
|
||||||
"win32 . ." \
|
"win32 . ." \
|
||||||
.../hello.txt \
|
.../hello.txt \
|
||||||
colon:test
|
colon:test \
|
||||||
|
"AUX.c" \
|
||||||
|
"abc/conOut\$ .xyz/test" \
|
||||||
|
lpt8 \
|
||||||
|
"lpt*" \
|
||||||
|
Nul \
|
||||||
|
"PRN./abc"
|
||||||
'
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
|
Loading…
Reference in a new issue