Merge branch 'win32-accommodate-funny-drive-names'

While the only permitted drive letters for physical drives on Windows
are letters of the US-English alphabet, this restriction does not apply
to virtual drives assigned via `subst <letter>: <path>`.

To prevent targeted attacks against systems where "funny" drive letters
such as `1` or `!` are assigned, let's handle them as regular drive
letters on Windows.

This fixes CVE-2019-1351.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
diff --git a/compat/mingw.c b/compat/mingw.c
index 11fb2de..22adb7a 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -1989,6 +1989,30 @@
 	return -1;
 }
 
+int mingw_has_dos_drive_prefix(const char *path)
+{
+	int i;
+
+	/*
+	 * Does it start with an ASCII letter (i.e. highest bit not set),
+	 * followed by a colon?
+	 */
+	if (!(0x80 & (unsigned char)*path))
+		return *path && path[1] == ':' ? 2 : 0;
+
+	/*
+	 * While drive letters must be letters of the English alphabet, it is
+	 * possible to assign virtually _any_ Unicode character via `subst` as
+	 * a drive letter to "virtual drives". Even `1`, or `ä`. Or fun stuff
+	 * like this:
+	 *
+	 *      subst ֍: %USERPROFILE%\Desktop
+	 */
+	for (i = 1; i < 4 && (0x80 & (unsigned char)path[i]); i++)
+		; /* skip first UTF-8 character */
+	return path[i] == ':' ? i + 1 : 0;
+}
+
 int mingw_skip_dos_drive_prefix(char **path)
 {
 	int ret = has_dos_drive_prefix(*path);
@@ -2137,6 +2161,8 @@
 	if (!protect_ntfs)
 		return 1;
 
+	skip_dos_drive_prefix((char **)&path);
+
 	for (;;) {
 		char c = *(path++);
 		switch (c) {
@@ -2158,6 +2184,14 @@
 			preceding_space_or_period = 1;
 			i++;
 			continue;
+		case ':': /* DOS drive prefix was already skipped */
+		case '<': case '>': case '"': case '|': case '?': case '*':
+			/* illegal character */
+			return 0;
+		default:
+			if (c > '\0' && c < '\x20')
+				/* illegal character */
+				return 0;
 		}
 		preceding_space_or_period = 0;
 		i++;
diff --git a/compat/mingw.h b/compat/mingw.h
index 8c49c1d..1706466 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -394,8 +394,8 @@
  * git specific compatibility
  */
 
-#define has_dos_drive_prefix(path) \
-	(isalpha(*(path)) && (path)[1] == ':' ? 2 : 0)
+int mingw_has_dos_drive_prefix(const char *path);
+#define has_dos_drive_prefix mingw_has_dos_drive_prefix
 int mingw_skip_dos_drive_prefix(char **path);
 #define skip_dos_drive_prefix mingw_skip_dos_drive_prefix
 static inline int mingw_is_dir_sep(int c)
@@ -431,8 +431,11 @@
 /**
  * Verifies that the given path is a valid one on Windows.
  *
- * In particular, path segments are disallowed which end in a period or a
- * space (except the special directories `.` and `..`).
+ * In particular, path segments are disallowed which
+ *
+ * - end in a period or a space (except the special directories `.` and `..`).
+ *
+ * - contain any of the reserved characters, e.g. `:`, `;`, `*`, etc
  *
  * Returns 1 upon success, otherwise 0.
  */
diff --git a/connect.c b/connect.c
index 49b28b8..a053cc2 100644
--- a/connect.c
+++ b/connect.c
@@ -264,7 +264,7 @@
 	const char *colon = strchr(url, ':');
 	const char *slash = strchr(url, '/');
 	return !colon || (slash && slash < colon) ||
-		has_dos_drive_prefix(url);
+		(has_dos_drive_prefix(url) && is_valid_path(url));
 }
 
 static const char *prot_name(enum protocol protocol)
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index 1171e0b..40db3e1 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -165,6 +165,15 @@
 	test_must_fail test-path-utils absolute_path ""
 '
 
+test_expect_success MINGW '<drive-letter>:\\abc is an absolute path' '
+	for letter in : \" C Z 1 ä
+	do
+		path=$letter:\\abc &&
+		absolute="$(test-path-utils absolute_path "$path")" &&
+		test "$path" = "$absolute" || return 1
+	done
+'
+
 test_expect_success 'real path rejects the empty string' '
 	test_must_fail test-path-utils real_path ""
 '
@@ -445,13 +454,15 @@
 		win32 \
 		"win32 x" \
 		../hello.txt \
+		C:\\git \
 		\
 		--not \
 		"win32 "  \
 		"win32 /x "  \
 		"win32."  \
 		"win32 . ." \
-		.../hello.txt
+		.../hello.txt \
+		colon:test
 '
 
 test_done