Merge pull request #4852 from libgit2/ethomson/unc_paths Win32 path canonicalization refactoring
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
diff --git a/src/win32/path_w32.c b/src/win32/path_w32.c
index 5e24260..b955b02 100644
--- a/src/win32/path_w32.c
+++ b/src/win32/path_w32.c
@@ -220,7 +220,7 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src)
goto on_error;
}
- /* Skip the drive letter specification ("C:") */
+ /* Skip the drive letter specification ("C:") */
if (git__utf8_to_16(dest + 2, MAX_PATH - 2, src) < 0)
goto on_error;
}
@@ -315,7 +315,7 @@ static bool path_is_volume(wchar_t *target, size_t target_len)
}
/* On success, returns the length, in characters, of the path stored in dest.
-* On failure, returns a negative value. */
+ * On failure, returns a negative value. */
int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path)
{
BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
@@ -360,16 +360,16 @@ int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path)
if (path_is_volume(target, target_len)) {
/* This path is a reparse point that represents another volume mounted
- * at this location, it is not a symbolic link our input was canonical.
- */
+ * at this location, it is not a symbolic link our input was canonical.
+ */
errno = EINVAL;
error = -1;
} else if (target_len) {
- /* The path may need to have a prefix removed. */
- target_len = git_win32__canonicalize_path(target, target_len);
+ /* The path may need to have a namespace prefix removed. */
+ target_len = git_win32_path_remove_namespace(target, target_len);
/* Need one additional character in the target buffer
- * for the terminating NULL. */
+ * for the terminating NULL. */
if (GIT_WIN_PATH_UTF16 > target_len) {
wcscpy(dest, target);
error = (int)target_len;
@@ -380,3 +380,97 @@ on_error:
CloseHandle(handle);
return error;
}
+
+/**
+ * Removes any trailing backslashes from a path, except in the case of a drive
+ * letter path (C:\, D:\, etc.). This function cannot fail.
+ *
+ * @param path The path which should be trimmed.
+ * @return The length of the modified string (<= the input length)
+ */
+size_t git_win32_path_trim_end(wchar_t *str, size_t len)
+{
+ while (1) {
+ if (!len || str[len - 1] != L'\\')
+ break;
+
+ /*
+ * Don't trim backslashes from drive letter paths, which
+ * are 3 characters long and of the form C:\, D:\, etc.
+ */
+ if (len == 3 && git_win32__isalpha(str[0]) && str[1] == ':')
+ break;
+
+ len--;
+ }
+
+ str[len] = L'\0';
+
+ return len;
+}
+
+/**
+ * Removes any of the following namespace prefixes from a path,
+ * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail.
+ *
+ * @param path The path which should be converted.
+ * @return The length of the modified string (<= the input length)
+ */
+size_t git_win32_path_remove_namespace(wchar_t *str, size_t len)
+{
+ static const wchar_t dosdevices_namespace[] = L"\\\?\?\\";
+ static const wchar_t nt_namespace[] = L"\\\\?\\";
+ static const wchar_t unc_namespace_remainder[] = L"UNC\\";
+ static const wchar_t unc_prefix[] = L"\\\\";
+
+ const wchar_t *prefix = NULL, *remainder = NULL;
+ size_t prefix_len = 0, remainder_len = 0;
+
+ /* "\??\" -- DOS Devices prefix */
+ if (len >= CONST_STRLEN(dosdevices_namespace) &&
+ !wcsncmp(str, dosdevices_namespace, CONST_STRLEN(dosdevices_namespace))) {
+ remainder = str + CONST_STRLEN(dosdevices_namespace);
+ remainder_len = len - CONST_STRLEN(dosdevices_namespace);
+ }
+ /* "\\?\" -- NT namespace prefix */
+ else if (len >= CONST_STRLEN(nt_namespace) &&
+ !wcsncmp(str, nt_namespace, CONST_STRLEN(nt_namespace))) {
+ remainder = str + CONST_STRLEN(nt_namespace);
+ remainder_len = len - CONST_STRLEN(nt_namespace);
+ }
+
+ /* "\??\UNC\", "\\?\UNC\" -- UNC prefix */
+ if (remainder_len >= CONST_STRLEN(unc_namespace_remainder) &&
+ !wcsncmp(remainder, unc_namespace_remainder, CONST_STRLEN(unc_namespace_remainder))) {
+
+ /*
+ * The proper Win32 path for a UNC share has "\\" at beginning of it
+ * and looks like "\\server\share\<folderStructure>". So remove the
+ * UNC namespace and add a prefix of "\\" in its place.
+ */
+ remainder += CONST_STRLEN(unc_namespace_remainder);
+ remainder_len -= CONST_STRLEN(unc_namespace_remainder);
+
+ prefix = unc_prefix;
+ prefix_len = CONST_STRLEN(unc_prefix);
+ }
+
+ if (remainder) {
+ /*
+ * Sanity check that the new string isn't longer than the old one.
+ * (This could only happen due to programmer error introducing a
+ * prefix longer than the namespace it replaces.)
+ */
+ assert(len >= remainder_len + prefix_len);
+
+ if (prefix)
+ memmove(str, prefix, prefix_len * sizeof(wchar_t));
+
+ memmove(str + prefix_len, remainder, remainder_len * sizeof(wchar_t));
+
+ len = remainder_len + prefix_len;
+ str[len] = L'\0';
+ }
+
+ return git_win32_path_trim_end(str, len);
+}
diff --git a/src/win32/path_w32.h b/src/win32/path_w32.h
index 83ffd1f..facbced 100644
--- a/src/win32/path_w32.h
+++ b/src/win32/path_w32.h
@@ -83,4 +83,22 @@ extern char *git_win32_path_8dot3_name(const char *path);
extern int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path);
+/**
+ * Removes any trailing backslashes from a path, except in the case of a drive
+ * letter path (C:\, D:\, etc.). This function cannot fail.
+ *
+ * @param path The path which should be trimmed.
+ * @return The length of the modified string (<= the input length)
+ */
+size_t git_win32_path_trim_end(wchar_t *str, size_t len);
+
+/**
+ * Removes any of the following namespace prefixes from a path,
+ * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail.
+ *
+ * @param path The path which should be converted.
+ * @return The length of the modified string (<= the input length)
+ */
+size_t git_win32_path_remove_namespace(wchar_t *str, size_t len);
+
#endif
diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c
index 8617e45..8c321ef 100644
--- a/src/win32/posix_w32.c
+++ b/src/win32/posix_w32.c
@@ -354,7 +354,7 @@ static int do_lstat(const char *path, struct stat *buf, bool posixly_correct)
if ((len = git_win32_path_from_utf8(path_w, path)) < 0)
return -1;
- git_win32__path_trim_end(path_w, len);
+ git_win32_path_trim_end(path_w, len);
return lstat_w(path_w, buf, posixly_correct);
}
@@ -648,8 +648,8 @@ static int getfinalpath_w(
if (!dwChars || dwChars >= GIT_WIN_PATH_UTF16)
return -1;
- /* The path may be delivered to us with a prefix; canonicalize */
- return (int)git_win32__canonicalize_path(dest, dwChars);
+ /* The path may be delivered to us with a namespace prefix; remove */
+ return (int)git_win32_path_remove_namespace(dest, dwChars);
}
static int follow_and_lstat_link(git_win32_path path, struct stat* buf)
diff --git a/src/win32/w32_util.c b/src/win32/w32_util.c
index b7b1ffa..5996c9f 100644
--- a/src/win32/w32_util.c
+++ b/src/win32/w32_util.c
@@ -93,71 +93,3 @@ int git_win32__hidden(bool *out, const char *path)
*out = (attrs & FILE_ATTRIBUTE_HIDDEN) ? true : false;
return 0;
}
-
-/**
- * Removes any trailing backslashes from a path, except in the case of a drive
- * letter path (C:\, D:\, etc.). This function cannot fail.
- *
- * @param path The path which should be trimmed.
- * @return The length of the modified string (<= the input length)
- */
-size_t git_win32__path_trim_end(wchar_t *str, size_t len)
-{
- while (1) {
- if (!len || str[len - 1] != L'\\')
- break;
-
- /* Don't trim backslashes from drive letter paths, which
- * are 3 characters long and of the form C:\, D:\, etc. */
- if (len == 3 && git_win32__isalpha(str[0]) && str[1] == ':')
- break;
-
- len--;
- }
-
- str[len] = L'\0';
-
- return len;
-}
-
-/**
- * Removes any of the following namespace prefixes from a path,
- * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail.
- *
- * @param path The path which should be converted.
- * @return The length of the modified string (<= the input length)
- */
-size_t git_win32__canonicalize_path(wchar_t *str, size_t len)
-{
- static const wchar_t dosdevices_prefix[] = L"\\\?\?\\";
- static const wchar_t nt_prefix[] = L"\\\\?\\";
- static const wchar_t unc_prefix[] = L"UNC\\";
- size_t to_advance = 0;
-
- /* "\??\" -- DOS Devices prefix */
- if (len >= CONST_STRLEN(dosdevices_prefix) &&
- !wcsncmp(str, dosdevices_prefix, CONST_STRLEN(dosdevices_prefix))) {
- to_advance += CONST_STRLEN(dosdevices_prefix);
- len -= CONST_STRLEN(dosdevices_prefix);
- }
- /* "\\?\" -- NT namespace prefix */
- else if (len >= CONST_STRLEN(nt_prefix) &&
- !wcsncmp(str, nt_prefix, CONST_STRLEN(nt_prefix))) {
- to_advance += CONST_STRLEN(nt_prefix);
- len -= CONST_STRLEN(nt_prefix);
- }
-
- /* "\??\UNC\", "\\?\UNC\" -- UNC prefix */
- if (to_advance && len >= CONST_STRLEN(unc_prefix) &&
- !wcsncmp(str + to_advance, unc_prefix, CONST_STRLEN(unc_prefix))) {
- to_advance += CONST_STRLEN(unc_prefix);
- len -= CONST_STRLEN(unc_prefix);
- }
-
- if (to_advance) {
- memmove(str, str + to_advance, len * sizeof(wchar_t));
- str[len] = L'\0';
- }
-
- return git_win32__path_trim_end(str, len);
-}
diff --git a/src/win32/w32_util.h b/src/win32/w32_util.h
index 6531f47..5216a13 100644
--- a/src/win32/w32_util.h
+++ b/src/win32/w32_util.h
@@ -60,24 +60,6 @@ extern int git_win32__set_hidden(const char *path, bool hidden);
extern int git_win32__hidden(bool *hidden, const char *path);
/**
- * Removes any trailing backslashes from a path, except in the case of a drive
- * letter path (C:\, D:\, etc.). This function cannot fail.
- *
- * @param path The path which should be trimmed.
- * @return The length of the modified string (<= the input length)
- */
-size_t git_win32__path_trim_end(wchar_t *str, size_t len);
-
-/**
- * Removes any of the following namespace prefixes from a path,
- * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail.
- *
- * @param path The path which should be converted.
- * @return The length of the modified string (<= the input length)
- */
-size_t git_win32__canonicalize_path(wchar_t *str, size_t len);
-
-/**
* Converts a FILETIME structure to a struct timespec.
*
* @param FILETIME A pointer to a FILETIME
diff --git a/tests/path/win32.c b/tests/path/win32.c
index 4ff0397..a5413c7 100644
--- a/tests/path/win32.c
+++ b/tests/path/win32.c
@@ -129,7 +129,7 @@ void test_path_win32__absolute_from_relative(void)
#endif
}
-void test_canonicalize(const wchar_t *in, const wchar_t *expected)
+static void test_canonicalize(const wchar_t *in, const wchar_t *expected)
{
#ifdef GIT_WIN32
git_win32_path canonical;
@@ -145,6 +145,55 @@ void test_canonicalize(const wchar_t *in, const wchar_t *expected)
#endif
}
+static void test_remove_namespace(const wchar_t *in, const wchar_t *expected)
+{
+#ifdef GIT_WIN32
+ git_win32_path canonical;
+
+ cl_assert(wcslen(in) < MAX_PATH);
+ wcscpy(canonical, in);
+
+ cl_must_pass(git_win32_path_remove_namespace(canonical, wcslen(in)));
+ cl_assert_equal_wcs(expected, canonical);
+#else
+ GIT_UNUSED(in);
+ GIT_UNUSED(expected);
+#endif
+}
+
+void test_path_win32__remove_namespace(void)
+{
+ test_remove_namespace(L"\\\\?\\C:\\Temp\\Foo", L"C:\\Temp\\Foo");
+ test_remove_namespace(L"\\\\?\\C:\\", L"C:\\");
+ test_remove_namespace(L"\\\\?\\", L"");
+
+ test_remove_namespace(L"\\??\\C:\\Temp\\Foo", L"C:\\Temp\\Foo");
+ test_remove_namespace(L"\\??\\C:\\", L"C:\\");
+ test_remove_namespace(L"\\??\\", L"");
+
+ test_remove_namespace(L"\\\\?\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder");
+ test_remove_namespace(L"\\\\?\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder");
+ test_remove_namespace(L"\\\\?\\UNC\\server\\C$", L"\\\\server\\C$");
+ test_remove_namespace(L"\\\\?\\UNC\\server\\", L"\\\\server");
+ test_remove_namespace(L"\\\\?\\UNC\\server", L"\\\\server");
+
+ test_remove_namespace(L"\\??\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder");
+ test_remove_namespace(L"\\??\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder");
+ test_remove_namespace(L"\\??\\UNC\\server\\C$", L"\\\\server\\C$");
+ test_remove_namespace(L"\\??\\UNC\\server\\", L"\\\\server");
+ test_remove_namespace(L"\\??\\UNC\\server", L"\\\\server");
+
+ test_remove_namespace(L"\\\\server\\C$\\folder", L"\\\\server\\C$\\folder");
+ test_remove_namespace(L"\\\\server\\C$", L"\\\\server\\C$");
+ test_remove_namespace(L"\\\\server\\", L"\\\\server");
+ test_remove_namespace(L"\\\\server", L"\\\\server");
+
+ test_remove_namespace(L"C:\\Foo\\Bar", L"C:\\Foo\\Bar");
+ test_remove_namespace(L"C:\\", L"C:\\");
+ test_remove_namespace(L"", L"");
+
+}
+
void test_path_win32__canonicalize(void)
{
#ifdef GIT_WIN32