summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2019-12-03 23:15:47 +1100
committerPatrick Steinhardt <ps@pks.im>2019-12-10 11:08:19 +0100
commita673ce14b814cfd785a80e3e54b52683e6427f70 (patch)
tree229a1afcbca4c15ee25ae96807f2e8d5885c8710
parent6bd0740125e58d6eb1a250fd6628cd15e6a34174 (diff)
downloadlibgit2-a673ce14b814cfd785a80e3e54b52683e6427f70.tar.gz
path: support non-ascii drive letters on dos
Windows/DOS only supports drive letters that are alpha characters A-Z. However, you can `subst` any one-character as a drive letter, including numbers or even emoji. Test that we can identify emoji as drive letters.
-rw-r--r--src/path.c38
-rw-r--r--tests/path/core.c11
2 files changed, 41 insertions, 8 deletions
diff --git a/src/path.c b/src/path.c
index 7397bb465..fa94f0058 100644
--- a/src/path.c
+++ b/src/path.c
@@ -21,7 +21,29 @@
#include <stdio.h>
#include <ctype.h>
-#define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':')
+static int dos_drive_prefix_length(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;
+}
#ifdef GIT_WIN32
static bool looks_like_network_computer_name(const char *path, int pos)
@@ -123,11 +145,11 @@ static int win32_prefix_length(const char *path, int len)
GIT_UNUSED(len);
#else
/*
- * Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return
- * 'C:/' here
+ * Mimic unix behavior where '/.git' returns '/': 'C:/.git'
+ * will return 'C:/' here
*/
- if (len == 2 && LOOKS_LIKE_DRIVE_PREFIX(path))
- return 2;
+ if (dos_drive_prefix_length(path) == len)
+ return len;
/*
* Similarly checks if we're dealing with a network computer name
@@ -260,11 +282,11 @@ const char *git_path_topdir(const char *path)
int git_path_root(const char *path)
{
- int offset = 0;
+ int offset = 0, prefix_len;
/* Does the root of the path look like a windows drive ? */
- if (LOOKS_LIKE_DRIVE_PREFIX(path))
- offset += 2;
+ if ((prefix_len = dos_drive_prefix_length(path)))
+ offset += prefix_len;
#ifdef GIT_WIN32
/* Are we dealing with a windows network path? */
diff --git a/tests/path/core.c b/tests/path/core.c
index bbcded083..95e5c8c94 100644
--- a/tests/path/core.c
+++ b/tests/path/core.c
@@ -352,3 +352,14 @@ void test_path_core__join_unrooted(void)
git_buf_dispose(&out);
}
+
+void test_path_core__join_unrooted_respects_funny_windows_roots(void)
+{
+ test_join_unrooted("๐Ÿ’ฉ:/foo/bar/foobar", 9, "bar/foobar", "๐Ÿ’ฉ:/foo");
+ test_join_unrooted("๐Ÿ’ฉ:/foo/bar/foobar", 13, "foobar", "๐Ÿ’ฉ:/foo/bar");
+ test_join_unrooted("๐Ÿ’ฉ:/foo", 5, "๐Ÿ’ฉ:/foo", "๐Ÿ’ฉ:/asdf");
+ test_join_unrooted("๐Ÿ’ฉ:/foo/bar", 5, "๐Ÿ’ฉ:/foo/bar", "๐Ÿ’ฉ:/asdf");
+ test_join_unrooted("๐Ÿ’ฉ:/foo/bar/foobar", 9, "๐Ÿ’ฉ:/foo/bar/foobar", "๐Ÿ’ฉ:/foo");
+ test_join_unrooted("๐Ÿ’ฉ:/foo/bar/foobar", 13, "๐Ÿ’ฉ:/foo/bar/foobar", "๐Ÿ’ฉ:/foo/bar");
+ test_join_unrooted("๐Ÿ’ฉ:/foo/bar/foobar", 9, "๐Ÿ’ฉ:/foo/bar/foobar", "๐Ÿ’ฉ:/foo/");
+}