Skip to content

Commit be36db2

Browse files
authored
Merge pull request libgit2#5435 from libgit2/ethomson/canonical
win32: don't canonicalize relative paths
2 parents e23b8b4 + 163db8f commit be36db2

File tree

5 files changed

+105
-14
lines changed

5 files changed

+105
-14
lines changed

src/win32/path_w32.c

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
#define path__is_unc(p) \
2626
(((p)[0] == '\\' && (p)[1] == '\\') || ((p)[0] == '/' && (p)[1] == '/'))
2727

28+
#define path__startswith_slash(p) \
29+
((p)[0] == '\\' || (p)[0] == '/')
30+
2831
GIT_INLINE(int) path__cwd(wchar_t *path, int size)
2932
{
3033
int len;
@@ -221,7 +224,7 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src)
221224
goto on_error;
222225
}
223226
/* Absolute paths omitting the drive letter */
224-
else if (src[0] == '\\' || src[0] == '/') {
227+
else if (path__startswith_slash(src)) {
225228
if (path__cwd(dest, MAX_PATH) < 0)
226229
goto on_error;
227230

@@ -257,6 +260,30 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src)
257260
return -1;
258261
}
259262

263+
int git_win32_path_relative_from_utf8(git_win32_path out, const char *src)
264+
{
265+
wchar_t *dest = out, *p;
266+
int len;
267+
268+
/* Handle absolute paths */
269+
if (git_path_is_absolute(src) ||
270+
path__is_nt_namespace(src) ||
271+
path__is_unc(src) ||
272+
path__startswith_slash(src)) {
273+
return git_win32_path_from_utf8(out, src);
274+
}
275+
276+
if ((len = git__utf8_to_16(dest, MAX_PATH, src)) < 0)
277+
return -1;
278+
279+
for (p = dest; p < (dest + len); p++) {
280+
if (*p == L'/')
281+
*p = L'\\';
282+
}
283+
284+
return len;
285+
}
286+
260287
int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src)
261288
{
262289
char *out = dest;

src/win32/path_w32.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,36 @@
1111
#include "vector.h"
1212

1313
/**
14-
* Create a Win32 path (in UCS-2 format) from a UTF-8 string.
14+
* Create a Win32 path (in UCS-2 format) from a UTF-8 string. If the given
15+
* path is relative, then it will be turned into an absolute path by having
16+
* the current working directory prepended.
1517
*
1618
* @param dest The buffer to receive the wide string.
1719
* @param src The UTF-8 string to convert.
1820
* @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
1921
*/
2022
extern int git_win32_path_from_utf8(git_win32_path dest, const char *src);
2123

24+
/**
25+
* Create a Win32 path (in UCS-2 format) from a UTF-8 string. If the given
26+
* path is relative, then it will not be turned into an absolute path.
27+
*
28+
* @param dest The buffer to receive the wide string.
29+
* @param src The UTF-8 string to convert.
30+
* @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
31+
*/
32+
extern int git_win32_path_relative_from_utf8(git_win32_path dest, const char *src);
33+
2234
/**
2335
* Canonicalize a Win32 UCS-2 path so that it is suitable for delivery to the
2436
* Win32 APIs: remove multiple directory separators, squashing to a single one,
2537
* strip trailing directory separators, ensure directory separators are all
2638
* canonical (always backslashes, never forward slashes) and process any
2739
* directory entries of '.' or '..'.
2840
*
41+
* Note that this is intended to be used on absolute Windows paths, those
42+
* that start with `C:\`, `\\server\share`, `\\?\`, etc.
43+
*
2944
* This processes the buffer in place.
3045
*
3146
* @param path The buffer to process

src/win32/posix_w32.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,8 +447,7 @@ int p_symlink(const char *target, const char *path)
447447
* relative symlinks, this is not someting we want.
448448
*/
449449
if (git_win32_path_from_utf8(path_w, path) < 0 ||
450-
git__utf8_to_16(target_w, MAX_PATH, target) < 0 ||
451-
git_win32_path_canonicalize(target_w) < 0)
450+
git_win32_path_relative_from_utf8(target_w, target) < 0)
452451
return -1;
453452

454453
dwFlags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;

tests/core/posix.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,26 @@ void test_core_posix__symlink_resolves_to_correct_type(void)
190190
git_buf_dispose(&contents);
191191
}
192192

193+
void test_core_posix__relative_symlink(void)
194+
{
195+
git_buf contents = GIT_BUF_INIT;
196+
197+
if (!git_path_supports_symlinks(clar_sandbox_path()))
198+
clar__skip();
199+
200+
cl_must_pass(git_futils_mkdir("dir", 0777, 0));
201+
cl_git_mkfile("file", "contents");
202+
cl_git_pass(p_symlink("../file", "dir/link"));
203+
cl_git_pass(git_futils_readbuffer(&contents, "dir/link"));
204+
cl_assert_equal_s(contents.ptr, "contents");
205+
206+
cl_must_pass(p_unlink("file"));
207+
cl_must_pass(p_unlink("dir/link"));
208+
cl_must_pass(p_rmdir("dir"));
209+
210+
git_buf_dispose(&contents);
211+
}
212+
193213
void test_core_posix__symlink_to_file_across_dirs(void)
194214
{
195215
git_buf contents = GIT_BUF_INIT;

tests/path/win32.c

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,21 @@ void test_utf8_to_utf16(const char *utf8_in, const wchar_t *utf16_expected)
2121
#endif
2222
}
2323

24+
void test_utf8_to_utf16_relative(const char* utf8_in, const wchar_t* utf16_expected)
25+
{
26+
#ifdef GIT_WIN32
27+
git_win32_path path_utf16;
28+
int path_utf16len;
29+
30+
cl_assert((path_utf16len = git_win32_path_relative_from_utf8(path_utf16, utf8_in)) >= 0);
31+
cl_assert_equal_wcs(utf16_expected, path_utf16);
32+
cl_assert_equal_i(wcslen(utf16_expected), path_utf16len);
33+
#else
34+
GIT_UNUSED(utf8_in);
35+
GIT_UNUSED(utf16_expected);
36+
#endif
37+
}
38+
2439
void test_path_win32__utf8_to_utf16(void)
2540
{
2641
#ifdef GIT_WIN32
@@ -129,6 +144,31 @@ void test_path_win32__absolute_from_relative(void)
129144
#endif
130145
}
131146

147+
void test_path_win32__keeps_relative(void)
148+
{
149+
#ifdef GIT_WIN32
150+
/* Relative paths stay relative */
151+
test_utf8_to_utf16_relative("Foo", L"Foo");
152+
test_utf8_to_utf16_relative("..\\..\\Foo", L"..\\..\\Foo");
153+
test_utf8_to_utf16_relative("Foo\\..", L"Foo\\..");
154+
test_utf8_to_utf16_relative("Foo\\..\\..", L"Foo\\..\\..");
155+
test_utf8_to_utf16_relative("Foo\\Bar", L"Foo\\Bar");
156+
test_utf8_to_utf16_relative("Foo\\..\\Bar", L"Foo\\..\\Bar");
157+
test_utf8_to_utf16_relative("../../Foo", L"..\\..\\Foo");
158+
test_utf8_to_utf16_relative("Foo/..", L"Foo\\..");
159+
test_utf8_to_utf16_relative("Foo/../..", L"Foo\\..\\..");
160+
test_utf8_to_utf16_relative("Foo/Bar", L"Foo\\Bar");
161+
test_utf8_to_utf16_relative("Foo/../Bar", L"Foo\\..\\Bar");
162+
test_utf8_to_utf16_relative("Foo/../Bar/", L"Foo\\..\\Bar\\");
163+
test_utf8_to_utf16_relative("", L"");
164+
165+
/* Absolute paths are canonicalized */
166+
test_utf8_to_utf16_relative("\\Foo", L"\\\\?\\C:\\Foo");
167+
test_utf8_to_utf16_relative("/Foo/Bar/", L"\\\\?\\C:\\Foo\\Bar");
168+
test_utf8_to_utf16_relative("\\\\server\\c$\\unc\\path", L"\\\\?\\UNC\\server\\c$\\unc\\path");
169+
#endif
170+
}
171+
132172
#ifdef GIT_WIN32
133173
static void test_canonicalize(const wchar_t *in, const wchar_t *expected)
134174
{
@@ -203,16 +243,6 @@ void test_path_win32__canonicalize(void)
203243
test_canonicalize(L"C:/Foo/Bar", L"C:\\Foo\\Bar");
204244
test_canonicalize(L"C:/", L"C:\\");
205245

206-
test_canonicalize(L"Foo\\\\Bar\\\\Asdf\\\\", L"Foo\\Bar\\Asdf");
207-
test_canonicalize(L"Foo\\\\Bar\\\\..\\\\Asdf\\", L"Foo\\Asdf");
208-
test_canonicalize(L"Foo\\\\Bar\\\\.\\\\Asdf\\", L"Foo\\Bar\\Asdf");
209-
test_canonicalize(L"Foo\\\\..\\Bar\\\\.\\\\Asdf\\", L"Bar\\Asdf");
210-
test_canonicalize(L"\\", L"");
211-
test_canonicalize(L"", L"");
212-
test_canonicalize(L"Foo\\..\\..\\..\\..", L"");
213-
test_canonicalize(L"..\\..\\..\\..", L"");
214-
test_canonicalize(L"\\..\\..\\..\\..", L"");
215-
216246
test_canonicalize(L"\\\\?\\C:\\Foo\\Bar", L"\\\\?\\C:\\Foo\\Bar");
217247
test_canonicalize(L"\\\\?\\C:\\Foo\\Bar\\", L"\\\\?\\C:\\Foo\\Bar");
218248
test_canonicalize(L"\\\\?\\C:\\\\Foo\\.\\Bar\\\\..\\", L"\\\\?\\C:\\Foo");

0 commit comments

Comments
 (0)