From 3410084675aa2b7bf5915ff022f89d4e0d66a1c5 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 26 Nov 2014 16:24:37 -0500 Subject: [PATCH 01/11] tests: use p_ instead of posix func directly --- tests/config/include.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/config/include.c b/tests/config/include.c index d4af59509..0a931342a 100644 --- a/tests/config/include.c +++ b/tests/config/include.c @@ -83,8 +83,8 @@ void test_config_include__depth(void) cl_git_fail(git_config_open_ondisk(&cfg, "a")); - unlink("a"); - unlink("b"); + p_unlink("a"); + p_unlink("b"); } void test_config_include__missing(void) From 09debe1213b9c979e21106ccbe9d420f8511f4eb Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 1 Dec 2014 13:06:11 -0500 Subject: [PATCH 02/11] clar: wide character comparisons --- tests/clar.c | 36 ++++++++++++++++++++++++++++++++++++ tests/clar.h | 6 ++++++ 2 files changed, 42 insertions(+) diff --git a/tests/clar.c b/tests/clar.c index 154644783..51f163526 100644 --- a/tests/clar.c +++ b/tests/clar.c @@ -11,6 +11,7 @@ #include #include #include +#include /* required for sandboxing */ #include @@ -525,6 +526,41 @@ void clar__assert_equal( } } } + else if (!strcmp("%ls", fmt)) { + const wchar_t *wcs1 = va_arg(args, const wchar_t *); + const wchar_t *wcs2 = va_arg(args, const wchar_t *); + is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1, wcs2); + + if (!is_equal) { + if (wcs1 && wcs2) { + int pos; + for (pos = 0; wcs1[pos] == wcs2[pos] && wcs1[pos] && wcs2[pos]; ++pos) + /* find differing byte offset */; + p_snprintf(buf, sizeof(buf), "'%ls' != '%ls' (at byte %d)", + wcs1, wcs2, pos); + } else { + p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'", wcs1, wcs2); + } + } + } + else if(!strcmp("%.*ls", fmt)) { + const wchar_t *wcs1 = va_arg(args, const wchar_t *); + const wchar_t *wcs2 = va_arg(args, const wchar_t *); + int len = va_arg(args, int); + is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1, wcs2, len); + + if (!is_equal) { + if (wcs1 && wcs2) { + int pos; + for (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++pos) + /* find differing byte offset */; + p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls' (at byte %d)", + len, wcs1, len, wcs2, pos); + } else { + p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls'", len, wcs1, len, wcs2); + } + } + } else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) { size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t); is_equal = (sz1 == sz2); diff --git a/tests/clar.h b/tests/clar.h index f9df72e8c..514203f89 100644 --- a/tests/clar.h +++ b/tests/clar.h @@ -74,9 +74,15 @@ void cl_fixture_cleanup(const char *fixture_name); #define cl_assert_equal_s(s1,s2) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%s", (s1), (s2)) #define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%s", (s1), (s2)) +#define cl_assert_equal_wcs(wcs1,wcs2) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%ls", (wcs1), (wcs2)) +#define cl_assert_equal_wcs_(wcs1,wcs2,note) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%ls", (wcs1), (wcs2)) + #define cl_assert_equal_strn(s1,s2,len) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%.*s", (s1), (s2), (int)(len)) #define cl_assert_equal_strn_(s1,s2,len,note) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%.*s", (s1), (s2), (int)(len)) +#define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%.*ls", (wcs1), (wcs2), (int)(len)) +#define cl_assert_equal_wcsn_(wcs1,wcs2,len,note) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%.*ls", (wcs1), (wcs2), (int)(len)) + #define cl_assert_equal_i(i1,i2) clar__assert_equal(__FILE__,__LINE__,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2)) #define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(__FILE__,__LINE__,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2)) #define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(__FILE__,__LINE__,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2)) From cceae9a25d0bed8b00f4981e051d5f380ef54401 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 1 Dec 2014 13:09:58 -0500 Subject: [PATCH 03/11] win32: use NT-prefixed "\\?\" paths When turning UTF-8 paths into UCS-2 paths for Windows, always use the \\?\-prefixed paths. Because this bypasses the system's path canonicalization, handle the canonicalization functions ourselves. We must: 1. always use a backslash as a directory separator 2. only use a single backslash between directories 3. not rely on the system to translate "." and ".." in paths 4. remove trailing backslashes, except at the drive root (C:\) --- src/win32/findfile.c | 1 + src/win32/path_w32.c | 269 ++++++++++++++++++++++++++++++++++++++++++ src/win32/path_w32.h | 65 ++++++++++ src/win32/posix.h | 1 + src/win32/posix_w32.c | 67 ++++------- src/win32/utf-conv.c | 42 ++++++- src/win32/utf-conv.h | 39 ------ src/win32/w32_util.h | 1 + tests/core/link.c | 19 +-- tests/path/win32.c | 190 +++++++++++++++++++++++++++++ 10 files changed, 591 insertions(+), 103 deletions(-) create mode 100644 src/win32/path_w32.c create mode 100644 src/win32/path_w32.h create mode 100644 tests/path/win32.c diff --git a/src/win32/findfile.c b/src/win32/findfile.c index 86d4ef5bd..de27dd060 100644 --- a/src/win32/findfile.c +++ b/src/win32/findfile.c @@ -5,6 +5,7 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "path_w32.h" #include "utf-conv.h" #include "path.h" #include "findfile.h" diff --git a/src/win32/path_w32.c b/src/win32/path_w32.c new file mode 100644 index 000000000..f0eacaa63 --- /dev/null +++ b/src/win32/path_w32.c @@ -0,0 +1,269 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "path.h" +#include "path_w32.h" +#include "utf-conv.h" + +#define PATH__NT_NAMESPACE L"\\\\?\\" +#define PATH__NT_NAMESPACE_LEN 4 + +#define PATH__ABSOLUTE_LEN 3 + +#define path__is_dirsep(p) ((p) == '/' || (p) == '\\') + +#define path__is_absolute(p) \ + (git__isalpha((p)[0]) && (p)[1] == ':' && ((p)[2] == '\\' || (p)[2] == '/')) + +#define path__is_nt_namespace(p) \ + (((p)[0] == '\\' && (p)[1] == '\\' && (p)[2] == '?' && (p)[3] == '\\') || \ + ((p)[0] == '/' && (p)[1] == '/' && (p)[2] == '?' && (p)[3] == '/')) + +#define path__is_unc(p) \ + (((p)[0] == '\\' && (p)[1] == '\\') || ((p)[0] == '/' && (p)[1] == '/')) + +GIT_INLINE(int) path__cwd(wchar_t *path, int size) +{ + int len; + + if ((len = GetCurrentDirectoryW(size, path)) == 0) { + errno = GetLastError() == ERROR_ACCESS_DENIED ? EACCES : ENOENT; + return -1; + } else if (len > size) { + errno = ENAMETOOLONG; + return -1; + } + + /* The Win32 APIs may return "\\?\" once you've used it first. + * But it may not. What a gloriously predictible API! + */ + if (wcsncmp(path, PATH__NT_NAMESPACE, PATH__NT_NAMESPACE_LEN)) + return len; + + len -= PATH__NT_NAMESPACE_LEN; + + memmove(path, path + PATH__NT_NAMESPACE_LEN, sizeof(wchar_t) * len); + return len; +} + +static wchar_t *path__skip_server(wchar_t *path) +{ + wchar_t *c; + + for (c = path; *c; c++) { + if (path__is_dirsep(*c)) + return c + 1; + } + + return c; +} + +static wchar_t *path__skip_prefix(wchar_t *path) +{ + if (path__is_nt_namespace(path)) { + path += PATH__NT_NAMESPACE_LEN; + + if (wcsncmp(path, L"UNC\\", 4) == 0) + path = path__skip_server(path + 4); + else if (path__is_absolute(path)) + path += PATH__ABSOLUTE_LEN; + } else if (path__is_absolute(path)) { + path += PATH__ABSOLUTE_LEN; + } else if (path__is_unc(path)) { + path = path__skip_server(path + 2); + } + + return path; +} + +int git_win32_path_canonicalize(git_win32_path path) +{ + wchar_t *base, *from, *to, *next; + size_t len; + + base = to = path__skip_prefix(path); + + /* Unposixify if the prefix */ + for (from = path; from < to; from++) { + if (*from == L'/') + *from = L'\\'; + } + + while (*from) { + for (next = from; *next; ++next) { + if (*next == L'/') { + *next = L'\\'; + break; + } + + if (*next == L'\\') + break; + } + + len = next - from; + + if (len == 1 && from[0] == L'.') + /* do nothing with singleton dot */; + + else if (len == 2 && from[0] == L'.' && from[1] == L'.') { + if (to == base) { + /* no more path segments to strip, eat the "../" */ + if (*next == L'\\') + len++; + + base = to; + } else { + /* back up a path segment */ + while (to > base && to[-1] == L'\\') to--; + while (to > base && to[-1] != L'\\') to--; + } + } else { + if (*next == L'\\' && *from != L'\\') + len++; + + if (to != from) + memmove(to, from, sizeof(wchar_t) * len); + + to += len; + } + + from += len; + + while (*from == L'\\') from++; + } + + /* Strip trailing backslashes */ + while (to > base && to[-1] == L'\\') to--; + + *to = L'\0'; + + return (to - path); +} + +int git_win32_path__cwd(wchar_t *out, size_t len) +{ + int cwd_len; + + if ((cwd_len = path__cwd(out, len)) < 0) + return -1; + + /* UNC paths */ + if (wcsncmp(L"\\\\", out, 2) == 0) { + /* Our buffer must be at least 5 characters larger than the + * current working directory: we swallow one of the leading + * '\'s, but we we add a 'UNC' specifier to the path, plus + * a trailing directory separator, plus a NUL. + */ + if (cwd_len > MAX_PATH - 4) { + errno = ENAMETOOLONG; + return -1; + } + + memmove(out+2, out, sizeof(wchar_t) * cwd_len); + out[0] = L'U'; + out[1] = L'N'; + out[2] = L'C'; + + cwd_len += 2; + } + + /* Our buffer must be at least 2 characters larger than the current + * working directory. (One character for the directory separator, + * one for the null. + */ + else if (cwd_len > MAX_PATH - 2) { + errno = ENAMETOOLONG; + return -1; + } + + return cwd_len; +} + +int git_win32_path_from_utf8(git_win32_path out, const char *src) +{ + wchar_t *dest = out; + + /* All win32 paths are in NT-prefixed format, beginning with "\\?\". */ + memcpy(dest, PATH__NT_NAMESPACE, sizeof(wchar_t) * PATH__NT_NAMESPACE_LEN); + dest += PATH__NT_NAMESPACE_LEN; + + /* See if this is an absolute path (beginning with a drive letter) */ + if (path__is_absolute(src)) { + if (git__utf8_to_16(dest, MAX_PATH, src) < 0) + return -1; + } + /* File-prefixed NT-style paths beginning with \\?\ */ + else if (path__is_nt_namespace(src)) { + /* Skip the NT prefix, the destination already contains it */ + if (git__utf8_to_16(dest, MAX_PATH, src + PATH__NT_NAMESPACE_LEN) < 0) + return -1; + } + /* UNC paths */ + else if (path__is_unc(src)) { + memcpy(dest, L"UNC\\", sizeof(wchar_t) * 4); + dest += 4; + + /* Skip the leading "\\" */ + if (git__utf8_to_16(dest, MAX_PATH - 2, src + 2) < 0) + return -1; + } + /* Absolute paths omitting the drive letter */ + else if (src[0] == '\\' || src[0] == '/') { + if (path__cwd(dest, MAX_PATH) < 0) + return -1; + + if (!path__is_absolute(dest)) { + errno = ENOENT; + return -1; + } + + /* Skip the drive letter specification ("C:") */ + if (git__utf8_to_16(dest + 2, MAX_PATH - 2, src) < 0) + return -1; + } + /* Relative paths */ + else { + int cwd_len; + + if ((cwd_len = git_win32_path__cwd(dest, MAX_PATH)) < 0) + return -1; + + dest[cwd_len++] = L'\\'; + + if (git__utf8_to_16(dest + cwd_len, MAX_PATH - cwd_len, src) < 0) + return -1; + } + + return git_win32_path_canonicalize(out); +} + +int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src) +{ + char *out = dest; + int len; + + /* Strip NT namespacing "\\?\" */ + if (path__is_nt_namespace(src)) { + src += 4; + + /* "\\?\UNC\server\share" -> "\\server\share" */ + if (wcsncmp(src, L"UNC\\", 4) == 0) { + src += 4; + + memcpy(dest, "\\\\", 2); + out = dest + 2; + } + } + + if ((len = git__utf16_to_8(out, GIT_WIN_PATH_UTF8, src)) < 0) + return len; + + git_path_mkposix(dest); + + return len; +} diff --git a/src/win32/path_w32.h b/src/win32/path_w32.h new file mode 100644 index 000000000..dc7a68e59 --- /dev/null +++ b/src/win32/path_w32.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git_path_w32_h__ +#define INCLUDE_git_path_w32_h__ + +/* + * Provides a large enough buffer to support Windows paths: MAX_PATH is + * 260, corresponding to a maximum path length of 259 characters plus a + * NULL terminator. Prefixing with "\\?\" adds 4 characters, but if the + * original was a UNC path, then we turn "\\server\share" into + * "\\?\UNC\server\share". So we replace the first two characters with + * 8 characters, a net gain of 6, so the maximum length is MAX_PATH+6. + */ +#define GIT_WIN_PATH_UTF16 MAX_PATH+6 + +/* Maximum size of a UTF-8 Win32 path. We remove the "\\?\" or "\\?\UNC\" + * prefixes for presentation, bringing us back to 259 (non-NULL) + * characters. UTF-8 does have 4-byte sequences, but they are encoded in + * UTF-16 using surrogate pairs, which takes up the space of two characters. + * Two characters in the range U+0800 -> U+FFFF take up more space in UTF-8 + * (6 bytes) than one surrogate pair (4 bytes). + */ +#define GIT_WIN_PATH_UTF8 (259 * 3 + 1) + +/* Win32 path types */ +typedef wchar_t git_win32_path[GIT_WIN_PATH_UTF16]; +typedef char git_win32_utf8_path[GIT_WIN_PATH_UTF8]; + +/** + * Create a Win32 path (in UCS-2 format) from a UTF-8 string. + * + * @param dest The buffer to receive the wide string. + * @param src The UTF-8 string to convert. + * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure + */ +extern int git_win32_path_from_utf8(git_win32_path dest, const char *src); + +/** + * Canonicalize a Win32 UCS-2 path so that it is suitable for delivery to the + * Win32 APIs: remove multiple directory separators, squashing to a single one, + * strip trailing directory separators, ensure directory separators are all + * canonical (always backslashes, never forward slashes) and process any + * directory entries of '.' or '..'. + * + * This processes the buffer in place. + * + * @param path The buffer to process + * @return The new length of the buffer, in wchar_t's (not counting the NULL terminator) + */ +extern int git_win32_path_canonicalize(git_win32_path path); + +/** + * Create an internal format (posix-style) UTF-8 path from a Win32 UCS-2 path. + * + * @param dest The buffer to receive the UTF-8 string. + * @param src The wide string to convert. + * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure + */ +extern int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src); + +#endif diff --git a/src/win32/posix.h b/src/win32/posix.h index e055a77d0..104966edc 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -9,6 +9,7 @@ #include "common.h" #include "../posix.h" +#include "path_w32.h" #include "utf-conv.h" #include "dir.h" diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 7b4555719..e446ccab0 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -7,6 +7,7 @@ #include "../posix.h" #include "../fileops.h" #include "path.h" +#include "path_w32.h" #include "utf-conv.h" #include "repository.h" #include "reparse.h" @@ -35,22 +36,6 @@ /* GetFinalPathNameByHandleW signature */ typedef DWORD(WINAPI *PFGetFinalPathNameByHandleW)(HANDLE, LPWSTR, DWORD, DWORD); -/* Helper function which converts UTF-8 paths to UTF-16. - * On failure, errno is set. */ -static int utf8_to_16_with_errno(git_win32_path dest, const char *src) -{ - int len = git_win32_path_from_utf8(dest, src); - - if (len < 0) { - if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) - errno = ENAMETOOLONG; - else - errno = EINVAL; /* Bad code point, presumably */ - } - - return len; -} - int p_ftruncate(int fd, long size) { #if defined(_MSC_VER) && _MSC_VER >= 1500 @@ -66,7 +51,7 @@ int p_mkdir(const char *path, mode_t mode) GIT_UNUSED(mode); - if (utf8_to_16_with_errno(buf, path) < 0) + if (git_win32_path_from_utf8(buf, path) < 0) return -1; return _wmkdir(buf); @@ -85,7 +70,7 @@ int p_unlink(const char *path) git_win32_path buf; int error; - if (utf8_to_16_with_errno(buf, path) < 0) + if (git_win32_path_from_utf8(buf, path) < 0) return -1; error = _wunlink(buf); @@ -292,7 +277,7 @@ static int do_lstat(const char *path, struct stat *buf, bool posixly_correct) git_win32_path path_w; int len; - if ((len = utf8_to_16_with_errno(path_w, path)) < 0) + if ((len = git_win32_path_from_utf8(path_w, path)) < 0) return -1; git_win32__path_trim_end(path_w, len); @@ -323,7 +308,7 @@ int p_readlink(const char *path, char *buf, size_t bufsiz) * could occur in the middle of the encoding of a code point, * we need to buffer the result on the stack. */ - if (utf8_to_16_with_errno(path_w, path) < 0 || + if (git_win32_path_from_utf8(path_w, path) < 0 || readlink_w(target_w, path_w) < 0 || (len = git_win32_path_to_utf8(target, target_w)) < 0) return -1; @@ -347,7 +332,7 @@ int p_open(const char *path, int flags, ...) git_win32_path buf; mode_t mode = 0; - if (utf8_to_16_with_errno(buf, path) < 0) + if (git_win32_path_from_utf8(buf, path) < 0) return -1; if (flags & O_CREAT) { @@ -365,7 +350,7 @@ int p_creat(const char *path, mode_t mode) { git_win32_path buf; - if (utf8_to_16_with_errno(buf, path) < 0) + if (git_win32_path_from_utf8(buf, path) < 0) return -1; return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | STANDARD_OPEN_FLAGS, mode); @@ -463,7 +448,7 @@ int p_stat(const char* path, struct stat* buf) git_win32_path path_w; int len; - if ((len = utf8_to_16_with_errno(path_w, path)) < 0) + if ((len = git_win32_path_from_utf8(path_w, path)) < 0) return -1; git_win32__path_trim_end(path_w, len); @@ -483,7 +468,7 @@ int p_chdir(const char* path) { git_win32_path buf; - if (utf8_to_16_with_errno(buf, path) < 0) + if (git_win32_path_from_utf8(buf, path) < 0) return -1; return _wchdir(buf); @@ -493,7 +478,7 @@ int p_chmod(const char* path, mode_t mode) { git_win32_path buf; - if (utf8_to_16_with_errno(buf, path) < 0) + if (git_win32_path_from_utf8(buf, path) < 0) return -1; return _wchmod(buf, mode); @@ -504,7 +489,7 @@ int p_rmdir(const char* path) git_win32_path buf; int error; - if (utf8_to_16_with_errno(buf, path) < 0) + if (git_win32_path_from_utf8(buf, path) < 0) return -1; error = _wrmdir(buf); @@ -533,7 +518,7 @@ char *p_realpath(const char *orig_path, char *buffer) { git_win32_path orig_path_w, buffer_w; - if (utf8_to_16_with_errno(orig_path_w, orig_path) < 0) + if (git_win32_path_from_utf8(orig_path_w, orig_path) < 0) return NULL; /* Note that if the path provided is a relative path, then the current directory @@ -554,20 +539,17 @@ char *p_realpath(const char *orig_path, char *buffer) return NULL; } - /* Convert the path to UTF-8. */ - if (buffer) { - /* If the caller provided a buffer, then it is assumed to be GIT_WIN_PATH_UTF8 - * characters in size. If it isn't, then we may overflow. */ - if (git__utf16_to_8(buffer, GIT_WIN_PATH_UTF8, buffer_w) < 0) - return NULL; - } else { - /* If the caller did not provide a buffer, then we allocate one for the caller - * from the heap. */ - if (git__utf16_to_8_alloc(&buffer, buffer_w) < 0) - return NULL; + if (!buffer && !(buffer = git__malloc(GIT_WIN_PATH_UTF8))) { + errno = ENOMEM; + return NULL; } - /* Convert backslashes to forward slashes */ + /* Convert the path to UTF-8. If the caller provided a buffer, then it + * is assumed to be GIT_WIN_PATH_UTF8 characters in size. If it isn't, + * then we may overflow. */ + if (git_win32_path_to_utf8(buffer, buffer_w) < 0) + return NULL; + git_path_mkposix(buffer); return buffer; @@ -608,6 +590,7 @@ int p_snprintf(char *buffer, size_t count, const char *format, ...) return r; } +/* TODO: wut? */ int p_mkstemp(char *tmp_path) { #if defined(_MSC_VER) && _MSC_VER >= 1500 @@ -625,7 +608,7 @@ int p_access(const char* path, mode_t mode) { git_win32_path buf; - if (utf8_to_16_with_errno(buf, path) < 0) + if (git_win32_path_from_utf8(buf, path) < 0) return -1; return _waccess(buf, mode); @@ -664,8 +647,8 @@ int p_rename(const char *from, const char *to) int rename_succeeded; int error; - if (utf8_to_16_with_errno(wfrom, from) < 0 || - utf8_to_16_with_errno(wto, to) < 0) + if (git_win32_path_from_utf8(wfrom, from) < 0 || + git_win32_path_from_utf8(wto, to) < 0) return -1; /* wait up to 50ms if file is locked by another thread or process */ diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c index b9ccfb5e5..b0205b019 100644 --- a/src/win32/utf-conv.c +++ b/src/win32/utf-conv.c @@ -26,6 +26,14 @@ GIT_INLINE(DWORD) get_wc_flags(void) return flags; } +GIT_INLINE(void) git__set_errno(void) +{ + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) + errno = ENAMETOOLONG; + else + errno = EINVAL; +} + /** * Converts a UTF-8 string to wide characters. * @@ -36,10 +44,15 @@ GIT_INLINE(DWORD) get_wc_flags(void) */ int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src) { + int len; + /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's * length. MultiByteToWideChar never returns int's minvalue, so underflow is not possible */ - return MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size) - 1; + if ((len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size) - 1) < 0) + git__set_errno(); + + return len; } /** @@ -52,10 +65,15 @@ int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src) */ int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src) { + int len; + /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's * length. WideCharToMultiByte never returns int's minvalue, so underflow is not possible */ - return WideCharToMultiByte(CP_UTF8, get_wc_flags(), src, -1, dest, (int)dest_size, NULL, NULL) - 1; + if ((len = WideCharToMultiByte(CP_UTF8, get_wc_flags(), src, -1, dest, (int)dest_size, NULL, NULL) - 1) < 0) + git__set_errno(); + + return len; } /** @@ -76,17 +94,23 @@ int git__utf8_to_16_alloc(wchar_t **dest, const char *src) /* Length of -1 indicates NULL termination of the input string */ utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, NULL, 0); - if (!utf16_size) + if (!utf16_size) { + git__set_errno(); return -1; + } *dest = git__malloc(utf16_size * sizeof(wchar_t)); - if (!*dest) + if (!*dest) { + errno = ENOMEM; return -1; + } utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, *dest, utf16_size); if (!utf16_size) { + git__set_errno(); + git__free(*dest); *dest = NULL; } @@ -116,17 +140,23 @@ int git__utf16_to_8_alloc(char **dest, const wchar_t *src) /* Length of -1 indicates NULL termination of the input string */ utf8_size = WideCharToMultiByte(CP_UTF8, dwFlags, src, -1, NULL, 0, NULL, NULL); - if (!utf8_size) + if (!utf8_size) { + git__set_errno(); return -1; + } *dest = git__malloc(utf8_size); - if (!*dest) + if (!*dest) { + errno = ENOMEM; return -1; + } utf8_size = WideCharToMultiByte(CP_UTF8, dwFlags, src, -1, *dest, utf8_size, NULL, NULL); if (!utf8_size) { + git__set_errno(); + git__free(*dest); *dest = NULL; } diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h index a480cd93e..89cdb96da 100644 --- a/src/win32/utf-conv.h +++ b/src/win32/utf-conv.h @@ -10,21 +10,6 @@ #include #include "common.h" -/* Equal to the Win32 MAX_PATH constant. The maximum path length is 259 - * characters plus a NULL terminator. */ -#define GIT_WIN_PATH_UTF16 260 - -/* Maximum size of a UTF-8 Win32 path. UTF-8 does have 4-byte sequences, - * but they are encoded in UTF-16 using surrogate pairs, which takes up - * the space of two characters. Two characters in the range U+0800 -> - * U+FFFF take up more space in UTF-8 (6 bytes) than one surrogate pair - * (4 bytes). */ -#define GIT_WIN_PATH_UTF8 (259 * 3 + 1) - -/* Win32 path types */ -typedef wchar_t git_win32_path[GIT_WIN_PATH_UTF16]; -typedef char git_win32_utf8_path[GIT_WIN_PATH_UTF8]; - /** * Converts a UTF-8 string to wide characters. * @@ -67,28 +52,4 @@ int git__utf8_to_16_alloc(wchar_t **dest, const char *src); */ int git__utf16_to_8_alloc(char **dest, const wchar_t *src); -/** - * Converts a UTF-8 Win32 path to wide characters. - * - * @param dest The buffer to receive the wide string. - * @param src The UTF-8 string to convert. - * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure - */ -GIT_INLINE(int) git_win32_path_from_utf8(git_win32_path dest, const char *src) -{ - return git__utf8_to_16(dest, GIT_WIN_PATH_UTF16, src); -} - -/** - * Converts a wide Win32 path to UTF-8. - * - * @param dest The buffer to receive the UTF-8 string. - * @param src The wide string to convert. - * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure - */ -GIT_INLINE(int) git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src) -{ - return git__utf16_to_8(dest, GIT_WIN_PATH_UTF8, src); -} - #endif diff --git a/src/win32/w32_util.h b/src/win32/w32_util.h index a1d388af5..9c1b94359 100644 --- a/src/win32/w32_util.h +++ b/src/win32/w32_util.h @@ -9,6 +9,7 @@ #define INCLUDE_w32_util_h__ #include "utf-conv.h" +#include "path_w32.h" GIT_INLINE(bool) git_win32__isalpha(wchar_t c) { diff --git a/tests/core/link.c b/tests/core/link.c index 83999ebdf..ec85ec4e0 100644 --- a/tests/core/link.c +++ b/tests/core/link.c @@ -197,19 +197,6 @@ static void do_custom_reparse(const char *path) #endif -git_buf *unslashify(git_buf *buf) -{ -#ifdef GIT_WIN32 - size_t i; - - for (i = 0; i < buf->size; i++) - if (buf->ptr[i] == '/') - buf->ptr[i] = '\\'; -#endif - - return buf; -} - void test_core_link__stat_regular_file(void) { struct stat st; @@ -584,7 +571,7 @@ void test_core_link__readlink_symlink(void) buf[len] = 0; - cl_assert_equal_s(git_buf_cstr(unslashify(&target_path)), buf); + cl_assert_equal_s(git_buf_cstr(&target_path), buf); git_buf_free(&target_path); } @@ -607,7 +594,7 @@ void test_core_link__readlink_dangling(void) buf[len] = 0; - cl_assert_equal_s(git_buf_cstr(unslashify(&target_path)), buf); + cl_assert_equal_s(git_buf_cstr(&target_path), buf); git_buf_free(&target_path); } @@ -636,7 +623,7 @@ void test_core_link__readlink_multiple(void) buf[len] = 0; - cl_assert_equal_s(git_buf_cstr(unslashify(&path2)), buf); + cl_assert_equal_s(git_buf_cstr(&path2), buf); git_buf_free(&path1); git_buf_free(&path2); diff --git a/tests/path/win32.c b/tests/path/win32.c new file mode 100644 index 000000000..ef0b5d2f2 --- /dev/null +++ b/tests/path/win32.c @@ -0,0 +1,190 @@ + +#include "clar_libgit2.h" +#include "path.h" + +#ifdef GIT_WIN32 +#include "win32/path_w32.h" +#endif + +void test_utf8_to_utf16(const char *utf8_in, const wchar_t *utf16_expected) +{ +#ifdef GIT_WIN32 + git_win32_path path_utf16; + int path_utf16len; + + cl_assert((path_utf16len = git_win32_path_from_utf8(path_utf16, utf8_in)) >= 0); + cl_assert_equal_wcs(utf16_expected, path_utf16); + cl_assert_equal_i(wcslen(utf16_expected), path_utf16len); +#else + GIT_UNUSED(utf8_in); + GIT_UNUSED(utf16_expected); +#endif +} + +void test_path_win32__utf8_to_utf16(void) +{ +#ifdef GIT_WIN32 + test_utf8_to_utf16("C:\\", L"\\\\?\\C:\\"); + test_utf8_to_utf16("c:\\", L"\\\\?\\c:\\"); + test_utf8_to_utf16("C:/", L"\\\\?\\C:\\"); + test_utf8_to_utf16("c:/", L"\\\\?\\c:\\"); +#endif +} + +void test_path_win32__removes_trailing_slash(void) +{ +#ifdef GIT_WIN32 + test_utf8_to_utf16("C:\\Foo\\", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("C:\\Foo\\\\", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("C:\\Foo\\\\", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("C:/Foo/", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("C:/Foo///", L"\\\\?\\C:\\Foo"); +#endif +} + +void test_path_win32__squashes_multiple_slashes(void) +{ +#ifdef GIT_WIN32 + test_utf8_to_utf16("C:\\\\Foo\\Bar\\\\Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); + test_utf8_to_utf16("C://Foo/Bar///Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); +#endif +} + +void test_path_win32__unc(void) +{ +#ifdef GIT_WIN32 + test_utf8_to_utf16("\\\\server\\c$\\unc\\path", L"\\\\?\\UNC\\server\\c$\\unc\\path"); + test_utf8_to_utf16("//server/git/style/unc/path", L"\\\\?\\UNC\\server\\git\\style\\unc\\path"); +#endif +} + +void test_path_win32__honors_max_path(void) +{ +#ifdef GIT_WIN32 + git_win32_path path_utf16; + + test_utf8_to_utf16("C:\\This path is 259 chars and is the max length in windows\\0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij", + L"\\\\?\\C:\\This path is 259 chars and is the max length in windows\\0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij"); + test_utf8_to_utf16("\\\\unc\\paths may also be 259 characters including the server\\123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij", + L"\\\\?\\UNC\\unc\\paths may also be 259 characters including the server\\123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij"); + + cl_check_fail(git_win32_path_from_utf8(path_utf16, "C:\\This path is 260 chars and is sadly too long for windows\\0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij")); + cl_check_fail(git_win32_path_from_utf8(path_utf16, "\\\\unc\\paths are also bound by 260 character restrictions\\including the server name portion\\bcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij")); +#endif +} + +void test_path_win32__dot_and_dotdot(void) +{ +#ifdef GIT_WIN32 + test_utf8_to_utf16("C:\\Foo\\..\\Foobar", L"\\\\?\\C:\\Foobar"); + test_utf8_to_utf16("C:\\Foo\\Bar\\..\\Foobar", L"\\\\?\\C:\\Foo\\Foobar"); + test_utf8_to_utf16("C:\\Foo\\Bar\\..\\Foobar\\..", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("C:\\Foobar\\..", L"\\\\?\\C:\\"); + test_utf8_to_utf16("C:/Foo/Bar/../Foobar", L"\\\\?\\C:\\Foo\\Foobar"); + test_utf8_to_utf16("C:/Foo/Bar/../Foobar/../Asdf/", L"\\\\?\\C:\\Foo\\Asdf"); + test_utf8_to_utf16("C:/Foo/Bar/../Foobar/..", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("C:/Foo/..", L"\\\\?\\C:\\"); + + test_utf8_to_utf16("C:\\Foo\\Bar\\.\\Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); + test_utf8_to_utf16("C:\\.\\Foo\\.\\Bar\\.\\Foobar\\.\\", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); + test_utf8_to_utf16("C:/Foo/Bar/./Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); + test_utf8_to_utf16("C:/Foo/../Bar/./Foobar/../", L"\\\\?\\C:\\Bar"); + + test_utf8_to_utf16("C:\\Foo\\..\\..\\Bar", L"\\\\?\\C:\\Bar"); +#endif +} + +void test_path_win32__absolute_from_no_drive_letter(void) +{ +#ifdef GIT_WIN32 + test_utf8_to_utf16("\\Foo", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("\\Foo\\Bar", L"\\\\?\\C:\\Foo\\Bar"); + test_utf8_to_utf16("/Foo/Bar", L"\\\\?\\C:\\Foo\\Bar"); +#endif +} + +void test_path_win32__absolute_from_relative(void) +{ +#ifdef GIT_WIN32 + char cwd_backup[MAX_PATH]; + + cl_must_pass(p_getcwd(cwd_backup, MAX_PATH)); + cl_must_pass(p_chdir("C:/")); + + test_utf8_to_utf16("Foo", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("..\\..\\Foo", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("Foo\\..", L"\\\\?\\C:\\"); + test_utf8_to_utf16("Foo\\..\\..", L"\\\\?\\C:\\"); + test_utf8_to_utf16("", L"\\\\?\\C:\\"); + + cl_must_pass(p_chdir("C:/Windows")); + + test_utf8_to_utf16("Foo", L"\\\\?\\C:\\Windows\\Foo"); + test_utf8_to_utf16("Foo\\Bar", L"\\\\?\\C:\\Windows\\Foo\\Bar"); + test_utf8_to_utf16("..\\Foo", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("Foo\\..\\Bar", L"\\\\?\\C:\\Windows\\Bar"); + test_utf8_to_utf16("", L"\\\\?\\C:\\Windows"); + + cl_must_pass(p_chdir(cwd_backup)); +#endif +} + +void test_canonicalize(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_canonicalize(canonical)); + cl_assert_equal_wcs(expected, canonical); +#else + GIT_UNUSED(in); + GIT_UNUSED(expected); +#endif +} + +void test_path_win32__canonicalize(void) +{ +#ifdef GIT_WIN32 + test_canonicalize(L"C:\\Foo\\Bar", L"C:\\Foo\\Bar"); + test_canonicalize(L"C:\\Foo\\", L"C:\\Foo"); + test_canonicalize(L"C:\\Foo\\\\", L"C:\\Foo"); + test_canonicalize(L"C:\\Foo\\..\\Bar", L"C:\\Bar"); + test_canonicalize(L"C:\\Foo\\..\\..\\Bar", L"C:\\Bar"); + test_canonicalize(L"C:\\Foo\\..\\..\\..\\..\\", L"C:\\"); + test_canonicalize(L"C:/Foo/Bar", L"C:\\Foo\\Bar"); + test_canonicalize(L"C:/", L"C:\\"); + + test_canonicalize(L"Foo\\\\Bar\\\\Asdf\\\\", L"Foo\\Bar\\Asdf"); + test_canonicalize(L"Foo\\\\Bar\\\\..\\\\Asdf\\", L"Foo\\Asdf"); + test_canonicalize(L"Foo\\\\Bar\\\\.\\\\Asdf\\", L"Foo\\Bar\\Asdf"); + test_canonicalize(L"Foo\\\\..\\Bar\\\\.\\\\Asdf\\", L"Bar\\Asdf"); + test_canonicalize(L"\\", L""); + test_canonicalize(L"", L""); + test_canonicalize(L"Foo\\..\\..\\..\\..", L""); + test_canonicalize(L"..\\..\\..\\..", L""); + test_canonicalize(L"\\..\\..\\..\\..", L""); + + test_canonicalize(L"\\\\?\\C:\\Foo\\Bar", L"\\\\?\\C:\\Foo\\Bar"); + test_canonicalize(L"\\\\?\\C:\\Foo\\Bar\\", L"\\\\?\\C:\\Foo\\Bar"); + test_canonicalize(L"\\\\?\\C:\\\\Foo\\.\\Bar\\\\..\\", L"\\\\?\\C:\\Foo"); + test_canonicalize(L"\\\\?\\C:\\\\", L"\\\\?\\C:\\"); + test_canonicalize(L"//?/C:/", L"\\\\?\\C:\\"); + test_canonicalize(L"//?/C:/../../Foo/", L"\\\\?\\C:\\Foo"); + test_canonicalize(L"//?/C:/Foo/../../", L"\\\\?\\C:\\"); + + test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder", L"\\\\?\\UNC\\server\\C$\\folder"); + test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder\\", L"\\\\?\\UNC\\server\\C$\\folder"); + test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder\\", L"\\\\?\\UNC\\server\\C$\\folder"); + test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder\\..\\..\\..\\..\\share\\", L"\\\\?\\UNC\\server\\share"); + + test_canonicalize(L"\\\\server\\share", L"\\\\server\\share"); + test_canonicalize(L"\\\\server\\share\\", L"\\\\server\\share"); + test_canonicalize(L"\\\\server\\share\\\\foo\\\\bar", L"\\\\server\\share\\foo\\bar"); + test_canonicalize(L"\\\\server\\\\share\\\\foo\\\\bar", L"\\\\server\\share\\foo\\bar"); + test_canonicalize(L"\\\\server\\share\\..\\foo", L"\\\\server\\foo"); + test_canonicalize(L"\\\\server\\..\\..\\share\\.\\foo", L"\\\\server\\share\\foo"); +#endif +} From 62155257d2d30d8f8d7108539df0681dce27ff61 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Tue, 25 Nov 2014 00:14:52 +0100 Subject: [PATCH 04/11] tree: Check for `.git` with case insensitivy --- src/tree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tree.c b/src/tree.c index 6b06dfd77..6246ff648 100644 --- a/src/tree.c +++ b/src/tree.c @@ -57,7 +57,7 @@ static int valid_entry_name(const char *filename) (*filename != '.' || (strcmp(filename, ".") != 0 && strcmp(filename, "..") != 0 && - strcmp(filename, DOT_GIT) != 0)); + strcasecmp(filename, DOT_GIT) != 0)); } static int entry_sort_cmp(const void *a, const void *b) From 0d388adc86946f3c8cddc2493b470d47a653c4a5 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Tue, 25 Nov 2014 00:58:03 +0100 Subject: [PATCH 05/11] index: Check for valid paths before creating an index entry --- src/index.c | 110 ++++++++++++++++++++++++++++++++++++++------ tests/index/tests.c | 105 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 190 insertions(+), 25 deletions(-) diff --git a/src/index.c b/src/index.c index d3bc081a5..644f0c8a7 100644 --- a/src/index.c +++ b/src/index.c @@ -762,19 +762,96 @@ void git_index_entry__init_from_stat( entry->file_size = st->st_size; } -static git_index_entry *index_entry_alloc(const char *path) +/* + * We fundamentally don't like some paths: we don't want + * dot or dot-dot anywhere, and for obvious reasons don't + * want to recurse into ".git" either. + * + * Also, we don't want double slashes or slashes at the + * end that can make pathnames ambiguous. + */ +static int verify_dotfile(const char *rest) +{ + /* + * The first character was '.', but that + * has already been discarded, we now test + * the rest. + */ + + /* "." is not allowed */ + if (*rest == '\0' || *rest == '/') + return -1; + + switch (*rest) { + /* + * ".git" followed by NUL or slash is bad. This + * shares the path end test with the ".." case. + */ + case 'g': + case 'G': + if (rest[1] != 'i' && rest[1] != 'I') + break; + if (rest[2] != 't' && rest[2] != 'T') + break; + rest += 2; + /* fallthrough */ + case '.': + if (rest[1] == '\0' || rest[1] == '/') + return -1; + } + return 0; +} + +static int verify_component(char c, const char *rest) +{ + if ((c == '.' && verify_dotfile(rest)) < 0 || c == '/' || c == '\0') { + giterr_set(GITERR_INDEX, "Invalid path component in index: '%c%s'", c, rest); + return -1; + } + return 0; +} + +static int verify_path(const char *path) +{ + char c; + + /* TODO: should we check this? */ + /* + if (has_dos_drive_prefix(path)) + return -1; + */ + + c = *path++; + if (verify_component(c, path) < 0) + return -1; + + while ((c = *path++) != '\0') { + if (c == '/') { + c = *path++; + if (verify_component(c, path) < 0) + return -1; + } + } + return 0; +} + +static int index_entry_create(git_index_entry **out, const char *path) { size_t pathlen = strlen(path); - struct entry_internal *entry = - git__calloc(sizeof(struct entry_internal) + pathlen + 1, 1); - if (!entry) - return NULL; + struct entry_internal *entry; + + if (verify_path(path) < 0) + return -1; + + entry = git__calloc(sizeof(struct entry_internal) + pathlen + 1, 1); + GITERR_CHECK_ALLOC(entry); entry->pathlen = pathlen; memcpy(entry->path, path, pathlen); entry->entry.path = entry->path; - return (git_index_entry *)entry; + *out = (git_index_entry *)entry; + return 0; } static int index_entry_init( @@ -790,14 +867,17 @@ static int index_entry_init( "Could not initialize index entry. " "Index is not backed up by an existing repository."); + if (index_entry_create(&entry, rel_path) < 0) + return -1; + /* write the blob to disk and get the oid and stat info */ error = git_blob__create_from_paths( &oid, &st, INDEX_OWNER(index), NULL, rel_path, 0, true); - if (error < 0) - return error; - entry = index_entry_alloc(rel_path); - GITERR_CHECK_ALLOC(entry); + if (error < 0) { + index_entry_free(entry); + return error; + } entry->id = oid; git_index_entry__init_from_stat(entry, &st, !index->distrust_filemode); @@ -862,11 +942,11 @@ static int index_entry_dup(git_index_entry **out, const git_index_entry *src) return 0; } - *out = entry = index_entry_alloc(src->path); - GITERR_CHECK_ALLOC(entry); + if (index_entry_create(&entry, src->path) < 0) + return -1; index_entry_cpy(entry, src); - + *out = entry; return 0; } @@ -2316,8 +2396,8 @@ static int read_tree_cb( if (git_buf_joinpath(&path, root, tentry->filename) < 0) return -1; - entry = index_entry_alloc(path.ptr); - GITERR_CHECK_ALLOC(entry); + if (index_entry_create(&entry, path.ptr) < 0) + return -1; entry->mode = tentry->attr; entry->id = tentry->oid; diff --git a/tests/index/tests.c b/tests/index/tests.c index 7d544e8f3..0464e7337 100644 --- a/tests/index/tests.c +++ b/tests/index/tests.c @@ -309,31 +309,116 @@ void test_index_tests__add_bypath_to_a_bare_repository_returns_EBAREPO(void) git_repository_free(bare_repo); } -/* Test that writing an invalid filename fails */ -void test_index_tests__write_invalid_filename(void) +static void add_invalid_filename(git_repository *repo, const char *fn) { - git_repository *repo; git_index *index; - git_oid expected; + git_buf path = GIT_BUF_INIT; - p_mkdir("read_tree", 0700); - - cl_git_pass(git_repository_init(&repo, "./read_tree", 0)); cl_git_pass(git_repository_index(&index, repo)); + cl_assert(git_index_entrycount(index) == 0); + + git_buf_joinpath(&path, "./invalid", fn); + + cl_git_mkfile(path.ptr, NULL); + cl_git_fail(git_index_add_bypath(index, fn)); + cl_must_pass(p_unlink(path.ptr)); cl_assert(git_index_entrycount(index) == 0); - cl_git_mkfile("./read_tree/.git/hello", NULL); + git_index_free(index); +} - cl_git_pass(git_index_add_bypath(index, ".git/hello")); +/* Test that writing an invalid filename fails */ +void test_index_tests__add_invalid_filename(void) +{ + git_repository *repo; + + p_mkdir("invalid", 0700); + + cl_git_pass(git_repository_init(&repo, "./invalid", 0)); + cl_must_pass(p_mkdir("./invalid/subdir", 0777)); + + add_invalid_filename(repo, ".git/hello"); + add_invalid_filename(repo, ".GIT/hello"); + add_invalid_filename(repo, ".GiT/hello"); + add_invalid_filename(repo, "./.git/hello"); + add_invalid_filename(repo, "./foo"); + add_invalid_filename(repo, "./bar"); + add_invalid_filename(repo, "subdir/../bar"); + + git_repository_free(repo); + + cl_fixture_cleanup("invalid"); +} + +static void replace_char(char *str, char in, char out) +{ + char *c = str; + + while (*c++) + if (*c == in) + *c = out; +} + +static void write_invalid_filename(git_repository *repo, const char *fn_orig) +{ + git_index *index; + git_oid expected; + const git_index_entry *entry; + git_buf path = GIT_BUF_INIT; + char *fn; + + cl_git_pass(git_repository_index(&index, repo)); + cl_assert(git_index_entrycount(index) == 0); + + /* + * Sneak a valid path into the index, we'll update it + * to an invalid path when we try to write the index. + */ + fn = git__strdup(fn_orig); + replace_char(fn, '/', '_'); + + git_buf_joinpath(&path, "./invalid", fn); + + cl_git_mkfile(path.ptr, NULL); + + cl_git_pass(git_index_add_bypath(index, fn)); + + cl_assert(entry = git_index_get_bypath(index, fn, 0)); + + /* kids, don't try this at home */ + replace_char((char *)entry->path, '_', '/'); /* write-tree */ cl_git_fail(git_index_write_tree(&expected, index)); + p_unlink(path.ptr); + + cl_git_pass(git_index_remove_all(index, NULL, NULL, NULL)); git_index_free(index); + git__free(fn); +} + +/* Test that writing an invalid filename fails */ +void test_index_tests__write_invalid_filename(void) +{ + git_repository *repo; + + p_mkdir("invalid", 0700); + + cl_git_pass(git_repository_init(&repo, "./invalid", 0)); + + write_invalid_filename(repo, ".git/hello"); + write_invalid_filename(repo, ".GIT/hello"); + write_invalid_filename(repo, ".GiT/hello"); + write_invalid_filename(repo, "./.git/hello"); + write_invalid_filename(repo, "./foo"); + write_invalid_filename(repo, "./bar"); + write_invalid_filename(repo, "foo/../bar"); + git_repository_free(repo); - cl_fixture_cleanup("read_tree"); + cl_fixture_cleanup("invalid"); } void test_index_tests__remove_entry(void) From a64119e3963fcd358ba914c9e1a81a890666d15a Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 25 Nov 2014 18:13:00 -0500 Subject: [PATCH 06/11] checkout: disallow bad paths on win32 Disallow: 1. paths with trailing dot 2. paths with trailing space 3. paths with trailing colon 4. paths that are 8.3 short names of .git folders ("GIT~1") 5. paths that are reserved path names (COM1, LPT1, etc). 6. paths with reserved DOS characters (colons, asterisks, etc) These paths would (without \\?\ syntax) be elided to other paths - for example, ".git." would be written as ".git". As a result, writing these paths literally (using \\?\ syntax) makes them hard to operate with from the shell, Windows Explorer or other tools. Disallow these. --- src/checkout.c | 28 +- src/index.c | 119 +++------ src/path.c | 148 +++++++++++ src/path.h | 38 +++ src/refdb_fs.c | 10 + src/repository.c | 25 ++ src/repository.h | 20 +- src/util.c | 15 ++ src/util.h | 1 + src/win32/path_w32.c | 36 +++ src/win32/path_w32.h | 15 ++ tests/checkout/nasty.c | 251 ++++++++++++++++++ tests/path/core.c | 194 ++++++++++++++ tests/path/win32.c | 24 ++ tests/resources/nasty/.gitted/HEAD | Bin 0 -> 23 bytes tests/resources/nasty/.gitted/index | Bin 0 -> 120 bytes .../02/28b21d477f67b9f7720565da9e760b84c8b85b | Bin 0 -> 133 bytes .../04/18f28a75dc0c4951c01842e0d794843a88178a | Bin 0 -> 46 bytes .../05/1229bf9d30ec923052ff42db8069ccdc17159d | Bin 0 -> 53 bytes .../09/9ed86cb8501ae483b1855c351fe1a506ac9631 | Bin 0 -> 133 bytes .../0a/78e40e54cc471c0415ca0680550f242e7843e2 | Bin 0 -> 51 bytes .../0d/45fb57852c2229346a800bd3fc58e32527a21c | Bin 0 -> 45 bytes .../12/12c12915820e1ad523b6305c0dcdefea8b7e97 | Bin 0 -> 133 bytes .../13/e5f8be09e8b7db074fb39b96e08215cc4a36f1 | Bin 0 -> 56 bytes .../16/a701796bc3670e5c2fdaeccb7f1280c60b373f | Bin 0 -> 62 bytes .../1b/31d55e0c53efbea6c670ece9057b76b5822eea | Bin 0 -> 132 bytes .../1e/3c845808fa5883aa4bcf2f882172edb72a7a32 | Bin 0 -> 134 bytes .../24/676d5e93f9fa7b568f38d7bce01772908e982b | Bin 0 -> 47 bytes .../26/b665c162f67acae67779445f3c7b9782b0a6d7 | Bin 0 -> 133 bytes .../27/db66b046536a0e4f64c4f8c3a490641c3fa5e5 | Bin 0 -> 44 bytes .../39/fb3af508440cf970b92767f6d081c811574d2a | Bin 0 -> 131 bytes .../44/14ac920acabc3eb00e3cf9375eeb0cb6859c15 | Bin 0 -> 135 bytes .../4a/a347c8bb0456230f43f34833c97b9f52c40f62 | Bin 0 -> 133 bytes .../53/41a7b545d71198b076b8ba3374a75c9a290640 | Bin 0 -> 133 bytes .../5d/1ee4f24f66dcd62a30248588d33804656b2073 | Bin 0 -> 46 bytes .../65/94bdbad86bbc8d3ed0806a23827203fbab56c6 | Bin 0 -> 132 bytes .../68/e8bce48725490c376d57ebc60f0170605951a5 | Bin 0 -> 58 bytes .../69/7dc3d723a018538eb819d5db2035c15109af73 | Bin 0 -> 132 bytes .../6c/1f5f6fec515d33036b44c596bfae28fc460cba | Bin 0 -> 47 bytes .../7d/4e382485ace068fb83b768ba1a1c674afbdc1d | Bin 0 -> 62 bytes .../7f/924ca37670afa06c7a481a2487b728b2c0185a | Bin 0 -> 47 bytes .../80/a8fe4f10626c50b3a4fd065a4604bafc9f30fa | Bin 0 -> 23 bytes .../82/482ad2e683edfc14f7de359e4f9a5e88909c51 | Bin 0 -> 45 bytes .../89/9ff28744bed5bece69c78ba752c7dc3e954629 | Bin 0 -> 136 bytes .../8b/cbb6e0c0f9554efd5401e1ec14a4b2595eb3bf | Bin 0 -> 134 bytes .../8c/e7a3ef59c3d602a0296321eb964218f3d52fae | Bin 0 -> 56 bytes .../8f/1dcd43aa0164eb6ec319c3ec8879ca5cf62c1e | Bin 0 -> 132 bytes .../91/602c85bb50dd834205edd30435b77d5bb9ccf0 | Bin 0 -> 131 bytes .../94/f37c29173c8fa45a232b17e745c82132b2fafd | Bin 0 -> 132 bytes .../96/3fdf003bf7261b9155c5748dc0945349b69e68 | Bin 0 -> 44 bytes .../9e/683cdaf9ea2727c891b4cf8f7f11e9e28a67ca | Bin 0 -> 50 bytes .../af/45aa1eb7edf804ed10f70efb96fd178527c17c | Bin 0 -> 58 bytes .../b8/3795b1e0eb54f22f7056119db132500d0cdc05 | Bin 0 -> 56 bytes .../bf/7ab4723fcc57ecc7fceccf591d6c4773491569 | Bin 0 -> 133 bytes .../c4/89e70ed6d9f6331770eae21a77d15afd11cd99 | Bin 0 -> 56 bytes .../c6/72414d4d08111145ef8202f21c95fa7e688aee | Bin 0 -> 56 bytes .../cc/bbfdb796f9b03298f5c7225e8f830784e1a3b1 | Bin 0 -> 136 bytes .../cd/44b4ea1066b3fa1d4b3baad8dc1531aec287a6 | Bin 0 -> 47 bytes .../d2/eb26d4938550487de59a017a7bfee8ca46b5f4 | Bin 0 -> 132 bytes .../dc/37c5f1521fb76fe1c1ac7b13187f9396a59247 | Bin 0 -> 58 bytes .../de/bdc4a004fda6141a17d9c297617be70d40248f | Bin 0 -> 133 bytes .../e2/377bdbc93b30a34ed5deefedded89b947ff8f4 | Bin 0 -> 132 bytes .../e3/99c4fc4c07cb7947d2f3d966bc374df6ccc691 | Bin 0 -> 131 bytes .../e4/edb361e51932b5ccedbc7ee41b4d3a4289aece | Bin 0 -> 50 bytes .../e8/7caf56c91ab8d14e4ee8eb56308533503d1885 | Bin 0 -> 133 bytes .../ed/4bc023f61dc345ff0084b922b229d24de206e7 | Bin 0 -> 47 bytes .../ef/6ed8a2b15f95795aed82a974b995cace02dbfe | Bin 0 -> 43 bytes .../fa/9cfdbeaaf3a91ff4b84d74412cd59d9b16a615 | Bin 0 -> 136 bytes .../fd/7a37d92197267e55e1fc0cc4f283a815bd79b8 | Bin 0 -> 43 bytes .../heads/dot_backslash_dotcapitalgit_path | Bin 0 -> 41 bytes .../.gitted/refs/heads/dot_dotcapitalgit_path | Bin 0 -> 41 bytes .../nasty/.gitted/refs/heads/dot_dotgit_path | Bin 0 -> 41 bytes .../nasty/.gitted/refs/heads/dot_dotgit_tree | Bin 0 -> 41 bytes .../nasty/.gitted/refs/heads/dot_git_colon | Bin 0 -> 41 bytes .../.gitted/refs/heads/dot_git_colon_stuff | Bin 0 -> 41 bytes .../nasty/.gitted/refs/heads/dot_git_dot | Bin 0 -> 41 bytes .../nasty/.gitted/refs/heads/dot_path | Bin 0 -> 41 bytes .../nasty/.gitted/refs/heads/dot_path_two | Bin 0 -> 41 bytes .../nasty/.gitted/refs/heads/dot_tree | Bin 0 -> 41 bytes .../refs/heads/dotcapitalgit_backslash_path | Bin 0 -> 41 bytes .../.gitted/refs/heads/dotcapitalgit_path | Bin 0 -> 41 bytes .../.gitted/refs/heads/dotcapitalgit_tree | Bin 0 -> 41 bytes .../refs/heads/dotdot_dotcapitalgit_path | Bin 0 -> 41 bytes .../.gitted/refs/heads/dotdot_dotgit_path | Bin 0 -> 41 bytes .../.gitted/refs/heads/dotdot_dotgit_tree | Bin 0 -> 41 bytes .../nasty/.gitted/refs/heads/dotdot_path | Bin 0 -> 41 bytes .../nasty/.gitted/refs/heads/dotdot_tree | Bin 0 -> 41 bytes .../.gitted/refs/heads/dotgit_backslash_path | Bin 0 -> 41 bytes .../nasty/.gitted/refs/heads/dotgit_path | Bin 0 -> 41 bytes .../nasty/.gitted/refs/heads/dotgit_tree | Bin 0 -> 41 bytes .../nasty/.gitted/refs/heads/git_tilde1 | Bin 0 -> 41 bytes .../nasty/.gitted/refs/heads/git_tilde2 | Bin 0 -> 41 bytes .../nasty/.gitted/refs/heads/git_tilde3 | Bin 0 -> 41 bytes .../resources/nasty/.gitted/refs/heads/master | Bin 0 -> 41 bytes 94 files changed, 834 insertions(+), 90 deletions(-) create mode 100644 tests/checkout/nasty.c create mode 100644 tests/resources/nasty/.gitted/HEAD create mode 100644 tests/resources/nasty/.gitted/index create mode 100644 tests/resources/nasty/.gitted/objects/02/28b21d477f67b9f7720565da9e760b84c8b85b create mode 100644 tests/resources/nasty/.gitted/objects/04/18f28a75dc0c4951c01842e0d794843a88178a create mode 100644 tests/resources/nasty/.gitted/objects/05/1229bf9d30ec923052ff42db8069ccdc17159d create mode 100644 tests/resources/nasty/.gitted/objects/09/9ed86cb8501ae483b1855c351fe1a506ac9631 create mode 100644 tests/resources/nasty/.gitted/objects/0a/78e40e54cc471c0415ca0680550f242e7843e2 create mode 100644 tests/resources/nasty/.gitted/objects/0d/45fb57852c2229346a800bd3fc58e32527a21c create mode 100644 tests/resources/nasty/.gitted/objects/12/12c12915820e1ad523b6305c0dcdefea8b7e97 create mode 100644 tests/resources/nasty/.gitted/objects/13/e5f8be09e8b7db074fb39b96e08215cc4a36f1 create mode 100644 tests/resources/nasty/.gitted/objects/16/a701796bc3670e5c2fdaeccb7f1280c60b373f create mode 100644 tests/resources/nasty/.gitted/objects/1b/31d55e0c53efbea6c670ece9057b76b5822eea create mode 100644 tests/resources/nasty/.gitted/objects/1e/3c845808fa5883aa4bcf2f882172edb72a7a32 create mode 100644 tests/resources/nasty/.gitted/objects/24/676d5e93f9fa7b568f38d7bce01772908e982b create mode 100644 tests/resources/nasty/.gitted/objects/26/b665c162f67acae67779445f3c7b9782b0a6d7 create mode 100644 tests/resources/nasty/.gitted/objects/27/db66b046536a0e4f64c4f8c3a490641c3fa5e5 create mode 100644 tests/resources/nasty/.gitted/objects/39/fb3af508440cf970b92767f6d081c811574d2a create mode 100644 tests/resources/nasty/.gitted/objects/44/14ac920acabc3eb00e3cf9375eeb0cb6859c15 create mode 100644 tests/resources/nasty/.gitted/objects/4a/a347c8bb0456230f43f34833c97b9f52c40f62 create mode 100644 tests/resources/nasty/.gitted/objects/53/41a7b545d71198b076b8ba3374a75c9a290640 create mode 100644 tests/resources/nasty/.gitted/objects/5d/1ee4f24f66dcd62a30248588d33804656b2073 create mode 100644 tests/resources/nasty/.gitted/objects/65/94bdbad86bbc8d3ed0806a23827203fbab56c6 create mode 100644 tests/resources/nasty/.gitted/objects/68/e8bce48725490c376d57ebc60f0170605951a5 create mode 100644 tests/resources/nasty/.gitted/objects/69/7dc3d723a018538eb819d5db2035c15109af73 create mode 100644 tests/resources/nasty/.gitted/objects/6c/1f5f6fec515d33036b44c596bfae28fc460cba create mode 100644 tests/resources/nasty/.gitted/objects/7d/4e382485ace068fb83b768ba1a1c674afbdc1d create mode 100644 tests/resources/nasty/.gitted/objects/7f/924ca37670afa06c7a481a2487b728b2c0185a create mode 100644 tests/resources/nasty/.gitted/objects/80/a8fe4f10626c50b3a4fd065a4604bafc9f30fa create mode 100644 tests/resources/nasty/.gitted/objects/82/482ad2e683edfc14f7de359e4f9a5e88909c51 create mode 100644 tests/resources/nasty/.gitted/objects/89/9ff28744bed5bece69c78ba752c7dc3e954629 create mode 100644 tests/resources/nasty/.gitted/objects/8b/cbb6e0c0f9554efd5401e1ec14a4b2595eb3bf create mode 100644 tests/resources/nasty/.gitted/objects/8c/e7a3ef59c3d602a0296321eb964218f3d52fae create mode 100644 tests/resources/nasty/.gitted/objects/8f/1dcd43aa0164eb6ec319c3ec8879ca5cf62c1e create mode 100644 tests/resources/nasty/.gitted/objects/91/602c85bb50dd834205edd30435b77d5bb9ccf0 create mode 100644 tests/resources/nasty/.gitted/objects/94/f37c29173c8fa45a232b17e745c82132b2fafd create mode 100644 tests/resources/nasty/.gitted/objects/96/3fdf003bf7261b9155c5748dc0945349b69e68 create mode 100644 tests/resources/nasty/.gitted/objects/9e/683cdaf9ea2727c891b4cf8f7f11e9e28a67ca create mode 100644 tests/resources/nasty/.gitted/objects/af/45aa1eb7edf804ed10f70efb96fd178527c17c create mode 100644 tests/resources/nasty/.gitted/objects/b8/3795b1e0eb54f22f7056119db132500d0cdc05 create mode 100644 tests/resources/nasty/.gitted/objects/bf/7ab4723fcc57ecc7fceccf591d6c4773491569 create mode 100644 tests/resources/nasty/.gitted/objects/c4/89e70ed6d9f6331770eae21a77d15afd11cd99 create mode 100644 tests/resources/nasty/.gitted/objects/c6/72414d4d08111145ef8202f21c95fa7e688aee create mode 100644 tests/resources/nasty/.gitted/objects/cc/bbfdb796f9b03298f5c7225e8f830784e1a3b1 create mode 100644 tests/resources/nasty/.gitted/objects/cd/44b4ea1066b3fa1d4b3baad8dc1531aec287a6 create mode 100644 tests/resources/nasty/.gitted/objects/d2/eb26d4938550487de59a017a7bfee8ca46b5f4 create mode 100644 tests/resources/nasty/.gitted/objects/dc/37c5f1521fb76fe1c1ac7b13187f9396a59247 create mode 100644 tests/resources/nasty/.gitted/objects/de/bdc4a004fda6141a17d9c297617be70d40248f create mode 100644 tests/resources/nasty/.gitted/objects/e2/377bdbc93b30a34ed5deefedded89b947ff8f4 create mode 100644 tests/resources/nasty/.gitted/objects/e3/99c4fc4c07cb7947d2f3d966bc374df6ccc691 create mode 100644 tests/resources/nasty/.gitted/objects/e4/edb361e51932b5ccedbc7ee41b4d3a4289aece create mode 100644 tests/resources/nasty/.gitted/objects/e8/7caf56c91ab8d14e4ee8eb56308533503d1885 create mode 100644 tests/resources/nasty/.gitted/objects/ed/4bc023f61dc345ff0084b922b229d24de206e7 create mode 100644 tests/resources/nasty/.gitted/objects/ef/6ed8a2b15f95795aed82a974b995cace02dbfe create mode 100644 tests/resources/nasty/.gitted/objects/fa/9cfdbeaaf3a91ff4b84d74412cd59d9b16a615 create mode 100644 tests/resources/nasty/.gitted/objects/fd/7a37d92197267e55e1fc0cc4f283a815bd79b8 create mode 100644 tests/resources/nasty/.gitted/refs/heads/dot_backslash_dotcapitalgit_path create mode 100644 tests/resources/nasty/.gitted/refs/heads/dot_dotcapitalgit_path create mode 100644 tests/resources/nasty/.gitted/refs/heads/dot_dotgit_path create mode 100644 tests/resources/nasty/.gitted/refs/heads/dot_dotgit_tree create mode 100644 tests/resources/nasty/.gitted/refs/heads/dot_git_colon create mode 100644 tests/resources/nasty/.gitted/refs/heads/dot_git_colon_stuff create mode 100644 tests/resources/nasty/.gitted/refs/heads/dot_git_dot create mode 100644 tests/resources/nasty/.gitted/refs/heads/dot_path create mode 100644 tests/resources/nasty/.gitted/refs/heads/dot_path_two create mode 100644 tests/resources/nasty/.gitted/refs/heads/dot_tree create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotcapitalgit_backslash_path create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotcapitalgit_path create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotcapitalgit_tree create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotdot_dotcapitalgit_path create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotdot_dotgit_path create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotdot_dotgit_tree create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotdot_path create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotdot_tree create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotgit_backslash_path create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotgit_path create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotgit_tree create mode 100644 tests/resources/nasty/.gitted/refs/heads/git_tilde1 create mode 100644 tests/resources/nasty/.gitted/refs/heads/git_tilde2 create mode 100644 tests/resources/nasty/.gitted/refs/heads/git_tilde3 create mode 100644 tests/resources/nasty/.gitted/refs/heads/master diff --git a/src/checkout.c b/src/checkout.c index 4e879e36f..a3a46011e 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -1172,6 +1172,30 @@ static int checkout_get_remove_conflicts( return checkout_conflicts_foreach(data, data->index, workdir, pathspec, checkout_conflict_append_remove, data); } +static int checkout_verify_paths( + git_repository *repo, + int action, + git_diff_delta *delta) +{ + unsigned int flags = GIT_PATH_REJECT_DEFAULTS | GIT_PATH_REJECT_DOT_GIT; + + if (action & CHECKOUT_ACTION__REMOVE) { + if (!git_path_isvalid(repo, delta->old_file.path, flags)) { + giterr_set(GITERR_CHECKOUT, "Cannot remove invalid path '%s'", delta->old_file.path); + return -1; + } + } + + if (action & ~CHECKOUT_ACTION__REMOVE) { + if (!git_path_isvalid(repo, delta->new_file.path, flags)) { + giterr_set(GITERR_CHECKOUT, "Cannot checkout to invalid path '%s'", delta->old_file.path); + return -1; + } + } + + return 0; +} + static int checkout_get_actions( uint32_t **actions_ptr, size_t **counts_ptr, @@ -1205,7 +1229,9 @@ static int checkout_get_actions( } git_vector_foreach(deltas, i, delta) { - error = checkout_action(&act, data, delta, workdir, &wditem, &pathspec); + if ((error = checkout_action(&act, data, delta, workdir, &wditem, &pathspec)) == 0) + error = checkout_verify_paths(data->repo, act, delta); + if (error != 0) goto fail; diff --git a/src/index.c b/src/index.c index 644f0c8a7..2db7c8ea5 100644 --- a/src/index.c +++ b/src/index.c @@ -762,86 +762,19 @@ void git_index_entry__init_from_stat( entry->file_size = st->st_size; } -/* - * We fundamentally don't like some paths: we don't want - * dot or dot-dot anywhere, and for obvious reasons don't - * want to recurse into ".git" either. - * - * Also, we don't want double slashes or slashes at the - * end that can make pathnames ambiguous. - */ -static int verify_dotfile(const char *rest) -{ - /* - * The first character was '.', but that - * has already been discarded, we now test - * the rest. - */ - - /* "." is not allowed */ - if (*rest == '\0' || *rest == '/') - return -1; - - switch (*rest) { - /* - * ".git" followed by NUL or slash is bad. This - * shares the path end test with the ".." case. - */ - case 'g': - case 'G': - if (rest[1] != 'i' && rest[1] != 'I') - break; - if (rest[2] != 't' && rest[2] != 'T') - break; - rest += 2; - /* fallthrough */ - case '.': - if (rest[1] == '\0' || rest[1] == '/') - return -1; - } - return 0; -} - -static int verify_component(char c, const char *rest) -{ - if ((c == '.' && verify_dotfile(rest)) < 0 || c == '/' || c == '\0') { - giterr_set(GITERR_INDEX, "Invalid path component in index: '%c%s'", c, rest); - return -1; - } - return 0; -} - -static int verify_path(const char *path) -{ - char c; - - /* TODO: should we check this? */ - /* - if (has_dos_drive_prefix(path)) - return -1; - */ - - c = *path++; - if (verify_component(c, path) < 0) - return -1; - - while ((c = *path++) != '\0') { - if (c == '/') { - c = *path++; - if (verify_component(c, path) < 0) - return -1; - } - } - return 0; -} - -static int index_entry_create(git_index_entry **out, const char *path) +static int index_entry_create( + git_index_entry **out, + git_repository *repo, + const char *path) { size_t pathlen = strlen(path); struct entry_internal *entry; - if (verify_path(path) < 0) + if (!git_path_isvalid(repo, path, + GIT_PATH_REJECT_DEFAULTS | GIT_PATH_REJECT_DOT_GIT)) { + giterr_set(GITERR_INDEX, "Invalid path: '%s'", path); return -1; + } entry = git__calloc(sizeof(struct entry_internal) + pathlen + 1, 1); GITERR_CHECK_ALLOC(entry); @@ -855,7 +788,9 @@ static int index_entry_create(git_index_entry **out, const char *path) } static int index_entry_init( - git_index_entry **entry_out, git_index *index, const char *rel_path) + git_index_entry **entry_out, + git_index *index, + const char *rel_path) { int error = 0; git_index_entry *entry = NULL; @@ -867,7 +802,7 @@ static int index_entry_init( "Could not initialize index entry. " "Index is not backed up by an existing repository."); - if (index_entry_create(&entry, rel_path) < 0) + if (index_entry_create(&entry, INDEX_OWNER(index), rel_path) < 0) return -1; /* write the blob to disk and get the oid and stat info */ @@ -933,7 +868,10 @@ static void index_entry_cpy(git_index_entry *tgt, const git_index_entry *src) tgt->path = tgt_path; /* reset to existing path data */ } -static int index_entry_dup(git_index_entry **out, const git_index_entry *src) +static int index_entry_dup( + git_index_entry **out, + git_repository *repo, + const git_index_entry *src) { git_index_entry *entry; @@ -942,7 +880,7 @@ static int index_entry_dup(git_index_entry **out, const git_index_entry *src) return 0; } - if (index_entry_create(&entry, src->path) < 0) + if (index_entry_create(&entry, repo, src->path) < 0) return -1; index_entry_cpy(entry, src); @@ -1211,7 +1149,7 @@ int git_index_add(git_index *index, const git_index_entry *source_entry) return -1; } - if ((ret = index_entry_dup(&entry, source_entry)) < 0 || + if ((ret = index_entry_dup(&entry, INDEX_OWNER(index), source_entry)) < 0 || (ret = index_insert(index, &entry, 1)) < 0) return ret; @@ -1331,9 +1269,9 @@ int git_index_conflict_add(git_index *index, assert (index); - if ((ret = index_entry_dup(&entries[0], ancestor_entry)) < 0 || - (ret = index_entry_dup(&entries[1], our_entry)) < 0 || - (ret = index_entry_dup(&entries[2], their_entry)) < 0) + if ((ret = index_entry_dup(&entries[0], INDEX_OWNER(index), ancestor_entry)) < 0 || + (ret = index_entry_dup(&entries[1], INDEX_OWNER(index), our_entry)) < 0 || + (ret = index_entry_dup(&entries[2], INDEX_OWNER(index), their_entry)) < 0) goto on_error; for (i = 0; i < 3; i++) { @@ -1850,7 +1788,10 @@ static int read_conflict_names(git_index *index, const char *buffer, size_t size } static size_t read_entry( - git_index_entry **out, const void *buffer, size_t buffer_size) + git_index_entry **out, + git_index *index, + const void *buffer, + size_t buffer_size) { size_t path_length, entry_size; const char *path_ptr; @@ -1914,7 +1855,7 @@ static size_t read_entry( entry.path = (char *)path_ptr; - if (index_entry_dup(out, &entry) < 0) + if (index_entry_dup(out, INDEX_OWNER(index), &entry) < 0) return 0; return entry_size; @@ -2015,7 +1956,7 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) /* Parse all the entries */ for (i = 0; i < header.entry_count && buffer_size > INDEX_FOOTER_SIZE; ++i) { git_index_entry *entry; - size_t entry_size = read_entry(&entry, buffer, buffer_size); + size_t entry_size = read_entry(&entry, index, buffer, buffer_size); /* 0 bytes read means an object corruption */ if (entry_size == 0) { @@ -2376,6 +2317,7 @@ int git_index_entry_stage(const git_index_entry *entry) } typedef struct read_tree_data { + git_index *index; git_vector *old_entries; git_vector *new_entries; git_vector_cmp entry_cmp; @@ -2396,7 +2338,7 @@ static int read_tree_cb( if (git_buf_joinpath(&path, root, tentry->filename) < 0) return -1; - if (index_entry_create(&entry, path.ptr) < 0) + if (index_entry_create(&entry, INDEX_OWNER(data->index), path.ptr) < 0) return -1; entry->mode = tentry->attr; @@ -2437,6 +2379,7 @@ int git_index_read_tree(git_index *index, const git_tree *tree) git_vector_set_cmp(&entries, index->entries._cmp); /* match sort */ + data.index = index; data.old_entries = &index->entries; data.new_entries = &entries; data.entry_cmp = index->entries_search; @@ -2556,7 +2499,7 @@ int git_index_add_all( break; /* make the new entry to insert */ - if ((error = index_entry_dup(&entry, wd)) < 0) + if ((error = index_entry_dup(&entry, INDEX_OWNER(index), wd)) < 0) break; entry->id = blobid; diff --git a/src/path.c b/src/path.c index effe2fff1..dbe193acb 100644 --- a/src/path.c +++ b/src/path.c @@ -7,6 +7,7 @@ #include "common.h" #include "path.h" #include "posix.h" +#include "repository.h" #ifdef GIT_WIN32 #include "win32/posix.h" #include "win32/w32_util.h" @@ -1238,3 +1239,150 @@ int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path) else return git_buf_sets(local_path_out, url_or_path); } + +GIT_INLINE(bool) verify_shortname( + git_repository *repo, + const char *component, + size_t len) +{ + const char *shortname_repo; + + if (len == git_repository__8dot3_default_len && + strncasecmp(git_repository__8dot3_default, component, len) == 0) + return false; + + if (repo && + (shortname_repo = git_repository__8dot3_name(repo)) && + shortname_repo != git_repository__8dot3_default && + git__prefixncmp_icase(component, len, shortname_repo) == 0) + return false; + + return true; +} + +/* Reject paths like AUX or COM1, or those versions that end in a dot or + * colon. ("AUX." or "AUX:") + */ +GIT_INLINE(bool) verify_dospath( + const char *component, + size_t len, + const char dospath[3], + bool trailing_num) +{ + size_t last = trailing_num ? 4 : 3; + + if (len < last || git__strncasecmp(component, dospath, 3) != 0) + return true; + + if (trailing_num && !git__isdigit(component[3])) + return true; + + return (len > last && + component[last] != '.' && + component[last] != ':'); +} + +GIT_INLINE(bool) verify_char(unsigned char c, unsigned int flags) +{ + if ((flags & GIT_PATH_REJECT_BACKSLASH) && c == '\\') + return false; + + if (flags & GIT_PATH_REJECT_NT_CHARS) { + if (c < 32) + return false; + + switch (c) { + case '<': + case '>': + case ':': + case '"': + case '|': + case '?': + case '*': + return false; + } + } + + return true; +} + +/* + * We fundamentally don't like some paths when dealing with user-inputted + * strings (in checkout or ref names): we don't want dot or dot-dot + * anywhere, we want to avoid writing weird paths on Windows that can't + * be handled by tools that use the non-\\?\ APIs, we don't want slashes + * or double slashes at the end of paths that can make them ambiguous. + * + * For checkout, we don't want to recurse into ".git" either. + */ +static bool verify_component( + git_repository *repo, + const char *component, + size_t len, + unsigned int flags) +{ + if (len == 0) + return false; + + if ((flags & GIT_PATH_REJECT_TRAVERSAL) && + len == 1 && component[0] == '.') + return false; + + if ((flags & GIT_PATH_REJECT_TRAVERSAL) && + len == 2 && component[0] == '.' && component[1] == '.') + return false; + + if ((flags & GIT_PATH_REJECT_DOT_GIT) && len == 4 && + component[0] == '.' && + (component[1] == 'g' || component[1] == 'G') && + (component[2] == 'i' || component[2] == 'I') && + (component[3] == 't' || component[3] == 'T')) + return false; + + if ((flags & GIT_PATH_REJECT_TRAILING_DOT) && component[len-1] == '.') + return false; + + if ((flags & GIT_PATH_REJECT_TRAILING_SPACE) && component[len-1] == ' ') + return false; + + if ((flags & GIT_PATH_REJECT_TRAILING_COLON) && component[len-1] == ':') + return false; + + if ((flags & GIT_PATH_REJECT_DOS_GIT_SHORTNAME) && + !verify_shortname(repo, component, len)) + return false; + + if (flags & GIT_PATH_REJECT_DOS_PATHS) { + if (!verify_dospath(component, len, "CON", false) || + !verify_dospath(component, len, "PRN", false) || + !verify_dospath(component, len, "AUX", false) || + !verify_dospath(component, len, "NUL", false) || + !verify_dospath(component, len, "COM", true) || + !verify_dospath(component, len, "LPT", true)) + return false; + } + + return true; +} + +bool git_path_isvalid( + git_repository *repo, + const char *path, + unsigned int flags) +{ + const char *start, *c; + + for (start = c = path; *c; c++) { + if (!verify_char(*c, flags)) + return false; + + if (*c == '/') { + if (!verify_component(repo, start, (c - start), flags)) + return false; + + start = c+1; + } + } + + return verify_component(repo, start, (c - start), flags); +} diff --git a/src/path.h b/src/path.h index 23d7c2ddb..a97668a98 100644 --- a/src/path.h +++ b/src/path.h @@ -462,4 +462,42 @@ extern bool git_path_does_fs_decompose_unicode(const char *root); extern bool git_path_is_local_file_url(const char *file_url); extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path); +/* Flags to determine path validity in `git_path_isvalid` */ +#define GIT_PATH_REJECT_TRAVERSAL (1 << 0) +#define GIT_PATH_REJECT_DOT_GIT (1 << 1) +#define GIT_PATH_REJECT_BACKSLASH (1 << 2) +#define GIT_PATH_REJECT_TRAILING_DOT (1 << 3) +#define GIT_PATH_REJECT_TRAILING_SPACE (1 << 4) +#define GIT_PATH_REJECT_TRAILING_COLON (1 << 5) +#define GIT_PATH_REJECT_DOS_GIT_SHORTNAME (1 << 6) +#define GIT_PATH_REJECT_DOS_PATHS (1 << 7) +#define GIT_PATH_REJECT_NT_CHARS (1 << 8) + +#ifdef GIT_WIN32 +# define GIT_PATH_REJECT_DEFAULTS \ + GIT_PATH_REJECT_TRAVERSAL | \ + GIT_PATH_REJECT_BACKSLASH | \ + GIT_PATH_REJECT_TRAILING_DOT | \ + GIT_PATH_REJECT_TRAILING_SPACE | \ + GIT_PATH_REJECT_TRAILING_COLON | \ + GIT_PATH_REJECT_DOS_GIT_SHORTNAME | \ + GIT_PATH_REJECT_DOS_PATHS | \ + GIT_PATH_REJECT_NT_CHARS +#else +# define GIT_PATH_REJECT_DEFAULTS GIT_PATH_REJECT_TRAVERSAL +#endif + +/* + * Determine whether a path is a valid git path or not - this must not contain + * a '.' or '..' component, or a component that is ".git" (in any case). + * + * `repo` is optional. If specified, it will be used to determine the short + * path name to reject (if `GIT_PATH_REJECT_DOS_SHORTNAME` is specified), + * in addition to the default of "git~1". + */ +extern bool git_path_isvalid( + git_repository *repo, + const char *path, + unsigned int flags); + #endif diff --git a/src/refdb_fs.c b/src/refdb_fs.c index f39ba4f9c..61d1cd56e 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -712,6 +712,11 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char * assert(file && backend && name); + if (!git_path_isvalid(backend->repo, name, GIT_PATH_REJECT_DEFAULTS)) { + giterr_set(GITERR_INVALID, "Invalid reference name '%s'.", name); + return GIT_EINVALIDSPEC; + } + /* Remove a possibly existing empty directory hierarchy * which name would collide with the reference name */ @@ -1653,6 +1658,11 @@ static int lock_reflog(git_filebuf *file, refdb_fs_backend *backend, const char repo = backend->repo; + if (!git_path_isvalid(backend->repo, refname, GIT_PATH_REJECT_DEFAULTS)) { + giterr_set(GITERR_INVALID, "Invalid reference name '%s'.", refname); + return GIT_EINVALIDSPEC; + } + if (retrieve_reflog_path(&log_path, repo, refname) < 0) return -1; diff --git a/src/repository.c b/src/repository.c index 2bab52919..74a966ef3 100644 --- a/src/repository.c +++ b/src/repository.c @@ -37,6 +37,9 @@ #define GIT_REPO_VERSION 0 +const char *git_repository__8dot3_default = "GIT~1"; +size_t git_repository__8dot3_default_len = 5; + static void set_odb(git_repository *repo, git_odb *odb) { if (odb) { @@ -120,6 +123,7 @@ void git_repository_free(git_repository *repo) git__free(repo->path_repository); git__free(repo->workdir); git__free(repo->namespace); + git__free(repo->name_8dot3); git__memzero(repo, sizeof(*repo)); git__free(repo); @@ -791,6 +795,27 @@ const char *git_repository_get_namespace(git_repository *repo) return repo->namespace; } +const char *git_repository__8dot3_name(git_repository *repo) +{ + if (!repo->has_8dot3) { + repo->has_8dot3 = 1; + +#ifdef GIT_WIN32 + if (!repo->is_bare) { + repo->name_8dot3 = git_win32_path_8dot3_name(repo->path_repository); + + /* We anticipate the 8.3 name is "GIT~1", so use a static for + * easy testing in the common case */ + if (strcasecmp(repo->name_8dot3, git_repository__8dot3_default) == 0) + repo->has_8dot3_default = 1; + } +#endif + } + + return repo->has_8dot3_default ? + git_repository__8dot3_default : repo->name_8dot3; +} + static int check_repositoryformatversion(git_config *config) { int version; diff --git a/src/repository.h b/src/repository.h index 40e54c1ca..d9b950aac 100644 --- a/src/repository.h +++ b/src/repository.h @@ -120,8 +120,11 @@ struct git_repository { char *path_repository; char *workdir; char *namespace; + char *name_8dot3; - unsigned is_bare:1; + unsigned is_bare:1, + has_8dot3:1, + has_8dot3_default:1; unsigned int lru_counter; git_cvar_value cvar_cache[GIT_CVAR_CACHE_MAX]; @@ -174,4 +177,19 @@ int git_repository__set_orig_head(git_repository *repo, const git_oid *orig_head int git_repository__cleanup_files(git_repository *repo, const char *files[], size_t files_len); +/* + * Gets the DOS-compatible 8.3 "short name". This will return only the + * short name for the repository directory (ie, "git~1" for ".git"). This + * will always return a pointer to `git_repository__8dot3_default` when + * "GIT~1" is the short name. This will return NULL for bare repositories, + * and systems that do not have a short name. + */ +const char *git_repository__8dot3_name(git_repository *repo); + +/* The default DOS-compatible 8.3 "short name" for a git repository, + * "GIT~1". + */ +extern const char *git_repository__8dot3_default; +extern size_t git_repository__8dot3_default_len; + #endif diff --git a/src/util.c b/src/util.c index 5c305950f..6b0efbea5 100644 --- a/src/util.c +++ b/src/util.c @@ -250,6 +250,21 @@ int git__prefixcmp_icase(const char *str, const char *prefix) return strncasecmp(str, prefix, strlen(prefix)); } +int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix) +{ + int s, p; + + while(str_n--) { + s = (unsigned char)tolower(*str++); + p = (unsigned char)tolower(*prefix++); + + if (s != p) + return s - p; + } + + return (0 - *prefix); +} + int git__suffixcmp(const char *str, const char *suffix) { size_t a = strlen(str); diff --git a/src/util.h b/src/util.h index 6e57ad8c3..17cc08987 100644 --- a/src/util.h +++ b/src/util.h @@ -106,6 +106,7 @@ GIT_INLINE(void) git__free(void *ptr) extern int git__prefixcmp(const char *str, const char *prefix); extern int git__prefixcmp_icase(const char *str, const char *prefix); +extern int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix); extern int git__suffixcmp(const char *str, const char *suffix); GIT_INLINE(int) git__signum(int val) diff --git a/src/win32/path_w32.c b/src/win32/path_w32.c index f0eacaa63..d66969c4d 100644 --- a/src/win32/path_w32.c +++ b/src/win32/path_w32.c @@ -267,3 +267,39 @@ int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src) return len; } + +char *git_win32_path_8dot3_name(const char *path) +{ + git_win32_path longpath, shortpath; + wchar_t *start; + char *shortname; + int len, namelen = 1; + + if (git_win32_path_from_utf8(longpath, path) < 0) + return NULL; + + len = GetShortPathNameW(longpath, shortpath, GIT_WIN_PATH_UTF16); + + while (len && shortpath[len-1] == L'\\') + shortpath[--len] = L'\0'; + + if (len == 0 || len >= GIT_WIN_PATH_UTF16) + return NULL; + + for (start = shortpath + (len - 1); + start > shortpath && *(start-1) != '/' && *(start-1) != '\\'; + start--) + namelen++; + + /* We may not have actually been given a short name. But if we have, + * it will be in the ASCII byte range, so we don't need to worry about + * multi-byte sequences and can allocate naively. + */ + if (namelen > 12 || (shortname = git__malloc(namelen + 1)) == NULL) + return NULL; + + if ((len = git__utf16_to_8(shortname, namelen + 1, start)) < 0) + return NULL; + + return shortname; +} diff --git a/src/win32/path_w32.h b/src/win32/path_w32.h index dc7a68e59..1d10166e8 100644 --- a/src/win32/path_w32.h +++ b/src/win32/path_w32.h @@ -26,6 +26,11 @@ */ #define GIT_WIN_PATH_UTF8 (259 * 3 + 1) +/* + * The length of a Windows "shortname", for 8.3 compatibility. + */ +#define GIT_WIN_PATH_SHORTNAME 13 + /* Win32 path types */ typedef wchar_t git_win32_path[GIT_WIN_PATH_UTF16]; typedef char git_win32_utf8_path[GIT_WIN_PATH_UTF8]; @@ -62,4 +67,14 @@ extern int git_win32_path_canonicalize(git_win32_path path); */ extern int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src); +/** + * Get the short name for the terminal path component in the given path. + * For example, given "C:\Foo\Bar\Asdf.txt", this will return the short name + * for the file "Asdf.txt". + * + * @param path The given path in UTF-8 + * @return The name of the shortname for the given path + */ +extern char *git_win32_path_8dot3_name(const char *path); + #endif diff --git a/tests/checkout/nasty.c b/tests/checkout/nasty.c new file mode 100644 index 000000000..8bc98f3d6 --- /dev/null +++ b/tests/checkout/nasty.c @@ -0,0 +1,251 @@ +#include "clar_libgit2.h" +#include "checkout_helpers.h" + +#include "git2/checkout.h" +#include "repository.h" +#include "buffer.h" +#include "fileops.h" + +static const char *repo_name = "nasty"; +static git_repository *repo; +static git_checkout_options checkout_opts; + +void test_checkout_nasty__initialize(void) +{ + repo = cl_git_sandbox_init(repo_name); + + GIT_INIT_STRUCTURE(&checkout_opts, GIT_CHECKOUT_OPTIONS_VERSION); + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; +} + +void test_checkout_nasty__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_checkout_fails(const char *refname, const char *filename) +{ + git_oid commit_id; + git_commit *commit; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_buf path = GIT_BUF_INIT; + + cl_git_pass(git_buf_joinpath(&path, repo_name, filename)); + + cl_git_pass(git_reference_name_to_id(&commit_id, repo, refname)); + cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_fail(git_checkout_tree(repo, (const git_object *)commit, &opts)); + cl_assert(!git_path_exists(path.ptr)); + + git_commit_free(commit); + git_buf_free(&path); +} + +/* A tree that contains ".git" as a tree, with a blob inside + * (".git/foobar"). + */ +void test_checkout_nasty__dotgit_tree(void) +{ + test_checkout_fails("refs/heads/dotgit_tree", ".git/foobar"); +} + +/* A tree that contains ".GIT" as a tree, with a blob inside + * (".GIT/foobar"). + */ +void test_checkout_nasty__dotcapitalgit_tree(void) +{ + test_checkout_fails("refs/heads/dotcapitalgit_tree", ".GIT/foobar"); +} + +/* A tree that contains a tree ".", with a blob inside ("./foobar"). + */ +void test_checkout_nasty__dot_tree(void) +{ + test_checkout_fails("refs/heads/dot_tree", "foobar"); +} + +/* A tree that contains a tree ".", with a tree ".git", with a blob + * inside ("./.git/foobar"). + */ +void test_checkout_nasty__dot_dotgit_tree(void) +{ + test_checkout_fails("refs/heads/dot_dotgit_tree", ".git/foobar"); +} + +/* A tree that contains a tree, with a tree "..", with a tree ".git", with a + * blob inside ("foo/../.git/foobar"). + */ +void test_checkout_nasty__dotdot_dotgit_tree(void) +{ + test_checkout_fails("refs/heads/dotdot_dotgit_tree", ".git/foobar"); +} + +/* A tree that contains a tree, with a tree "..", with a blob inside + * ("foo/../foobar"). + */ +void test_checkout_nasty__dotdot_tree(void) +{ + test_checkout_fails("refs/heads/dotdot_tree", "foobar"); +} + +/* A tree that contains a blob with the rogue name ".git/foobar" */ +void test_checkout_nasty__dotgit_path(void) +{ + test_checkout_fails("refs/heads/dotgit_path", ".git/foobar"); +} + +/* A tree that contains a blob with the rogue name ".GIT/foobar" */ +void test_checkout_nasty__dotcapitalgit_path(void) +{ + test_checkout_fails("refs/heads/dotcapitalgit_path", ".GIT/foobar"); +} + +/* A tree that contains a blob with the rogue name "./.git/foobar" */ +void test_checkout_nasty__dot_dotgit_path(void) +{ + test_checkout_fails("refs/heads/dot_dotgit_path", ".git/foobar"); +} + +/* A tree that contains a blob with the rogue name "./.GIT/foobar" */ +void test_checkout_nasty__dot_dotcapitalgit_path(void) +{ + test_checkout_fails("refs/heads/dot_dotcapitalgit_path", ".GIT/foobar"); +} + +/* A tree that contains a blob with the rogue name "foo/../.git/foobar" */ +void test_checkout_nasty__dotdot_dotgit_path(void) +{ + test_checkout_fails("refs/heads/dotdot_dotgit_path", ".git/foobar"); +} + +/* A tree that contains a blob with the rogue name "foo/../.GIT/foobar" */ +void test_checkout_nasty__dotdot_dotcapitalgit_path(void) +{ + test_checkout_fails("refs/heads/dotdot_dotcapitalgit_path", ".GIT/foobar"); +} + +/* A tree that contains a blob with the rogue name "foo/." */ +void test_checkout_nasty__dot_path(void) +{ + test_checkout_fails("refs/heads/dot_path", "./foobar"); +} + +/* A tree that contains a blob with the rogue name "foo/." */ +void test_checkout_nasty__dot_path_two(void) +{ + test_checkout_fails("refs/heads/dot_path_two", "foo/."); +} + +/* A tree that contains a blob with the rogue name "foo/../foobar" */ +void test_checkout_nasty__dotdot_path(void) +{ + test_checkout_fails("refs/heads/dotdot_path", "foobar"); +} + +/* A tree that contains an entry with a backslash ".git\foobar" */ +void test_checkout_nasty__dotgit_backslash_path(void) +{ +#ifdef GIT_WIN32 + test_checkout_fails("refs/heads/dotgit_backslash_path", ".git/foobar"); +#endif +} + +/* A tree that contains an entry with a backslash ".GIT\foobar" */ +void test_checkout_nasty__dotcapitalgit_backslash_path(void) +{ +#ifdef GIT_WIN32 + test_checkout_fails("refs/heads/dotcapitalgit_backslash_path", ".GIT/foobar"); +#endif +} + +/* A tree that contains an entry with a backslash ".\.GIT\foobar" */ +void test_checkout_nasty__dot_backslash_dotcapitalgit_path(void) +{ +#ifdef GIT_WIN32 + test_checkout_fails("refs/heads/dot_backslash_dotcapitalgit_path", ".GIT/foobar"); +#endif +} + +/* A tree that contains an entry ".git.", because Win32 APIs will drop the + * trailing slash. + */ +void test_checkout_nasty__dot_git_dot(void) +{ +#ifdef GIT_WIN32 + test_checkout_fails("refs/heads/dot_git_dot", ".git/foobar"); +#endif +} + +/* A tree that contains an entry "git~1", because that is typically the + * short name for ".git". + */ +void test_checkout_nasty__git_tilde1(void) +{ +#ifdef GIT_WIN32 + test_checkout_fails("refs/heads/git_tilde1", ".git/foobar"); +#endif +} + +/* A tree that contains an entry "git~2", when we have forced the short + * name for ".git" into "GIT~2". + */ +void test_checkout_nasty__git_custom_shortname(void) +{ +#ifdef GIT_WIN32 + cl_must_pass(p_rename("nasty/.git", "nasty/_temp")); + cl_git_write2file("nasty/git~1", "", 0, O_RDWR|O_CREAT, 0666); + cl_must_pass(p_rename("nasty/_temp", "nasty/.git")); + test_checkout_fails("refs/heads/git_tilde2", ".git/foobar"); +#endif +} + +/* A tree that contains an entry "git~3", which should be allowed, since + * it is not the typical short name ("GIT~1") or the actual short name + * ("GIT~2") for ".git". + */ +void test_checkout_nasty__only_looks_like_a_git_shortname(void) +{ +#ifdef GIT_WIN32 + git_oid commit_id; + git_commit *commit; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + cl_must_pass(p_rename("nasty/.git", "nasty/_temp")); + cl_git_write2file("nasty/git~1", "", 0, O_RDWR|O_CREAT, 0666); + cl_must_pass(p_rename("nasty/_temp", "nasty/.git")); + + cl_git_pass(git_reference_name_to_id(&commit_id, repo, "refs/heads/git_tilde3")); + cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_checkout_tree(repo, (const git_object *)commit, &opts)); + cl_assert(git_path_exists("nasty/git~3/foobar")); + + git_commit_free(commit); +#endif +} + +/* A tree that contains an entry "git:", because Win32 APIs will reject + * that as looking too similar to a drive letter. + */ +void test_checkout_nasty__dot_git_colon(void) +{ +#ifdef GIT_WIN32 + test_checkout_fails("refs/heads/dot_git_colon", ".git/foobar"); +#endif +} + +/* A tree that contains an entry "git:foo", because Win32 APIs will turn + * that into ".git". + */ +void test_checkout_nasty__dot_git_colon_stuff(void) +{ +#ifdef GIT_WIN32 + test_checkout_fails("refs/heads/dot_git_colon_stuff", ".git/foobar"); +#endif +} + diff --git a/tests/path/core.c b/tests/path/core.c index 45f54df29..7cc111800 100644 --- a/tests/path/core.c +++ b/tests/path/core.c @@ -51,3 +51,197 @@ void test_path_core__make_relative(void) test_make_relative("/path", "/path", "pathtofoo", GIT_ENOTFOUND); test_make_relative("path", "path", "pathtofoo", GIT_ENOTFOUND); } + +void test_path_core__isvalid_standard(void) +{ + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/bar", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/bar/file.txt", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/bar/.file", 0)); +} + +void test_path_core__isvalid_empty_dir_component(void) +{ + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo//bar", 0)); + + /* leading slash */ + cl_assert_equal_b(false, git_path_isvalid(NULL, "/", 0)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "/foo", 0)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "/foo/bar", 0)); + + /* trailing slash */ + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/", 0)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/bar/", 0)); +} + +void test_path_core__isvalid_dot_and_dotdot(void) +{ + cl_assert_equal_b(true, git_path_isvalid(NULL, ".", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "./foo", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/.", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "./foo", 0)); + + cl_assert_equal_b(true, git_path_isvalid(NULL, "..", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "../foo", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/..", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "../foo", 0)); + + cl_assert_equal_b(false, git_path_isvalid(NULL, ".", GIT_PATH_REJECT_TRAVERSAL)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "./foo", GIT_PATH_REJECT_TRAVERSAL)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.", GIT_PATH_REJECT_TRAVERSAL)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "./foo", GIT_PATH_REJECT_TRAVERSAL)); + + cl_assert_equal_b(false, git_path_isvalid(NULL, "..", GIT_PATH_REJECT_TRAVERSAL)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "../foo", GIT_PATH_REJECT_TRAVERSAL)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/..", GIT_PATH_REJECT_TRAVERSAL)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "../foo", GIT_PATH_REJECT_TRAVERSAL)); +} + +void test_path_core__isvalid_dot_git(void) +{ + cl_assert_equal_b(true, git_path_isvalid(NULL, ".git", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, ".git/foo", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/.git", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/.git/bar", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/.GIT/bar", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/bar/.Git", 0)); + + cl_assert_equal_b(false, git_path_isvalid(NULL, ".git", GIT_PATH_REJECT_DOT_GIT)); + cl_assert_equal_b(false, git_path_isvalid(NULL, ".git/foo", GIT_PATH_REJECT_DOT_GIT)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.git", GIT_PATH_REJECT_DOT_GIT)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.git/bar", GIT_PATH_REJECT_DOT_GIT)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.GIT/bar", GIT_PATH_REJECT_DOT_GIT)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/bar/.Git", GIT_PATH_REJECT_DOT_GIT)); + + cl_assert_equal_b(true, git_path_isvalid(NULL, "!git", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/!git", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "!git/bar", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, ".tig", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/.tig", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, ".tig/bar", 0)); +} + +void test_path_core__isvalid_backslash(void) +{ + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo\\file.txt", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/bar\\file.txt", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/bar\\", 0)); + + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo\\file.txt", GIT_PATH_REJECT_BACKSLASH)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/bar\\file.txt", GIT_PATH_REJECT_BACKSLASH)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/bar\\", GIT_PATH_REJECT_BACKSLASH)); +} + +void test_path_core__isvalid_trailing_dot(void) +{ + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo.", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo...", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/bar.", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo./bar", 0)); + + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo.", GIT_PATH_REJECT_TRAILING_DOT)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo...", GIT_PATH_REJECT_TRAILING_DOT)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/bar.", GIT_PATH_REJECT_TRAILING_DOT)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo./bar", GIT_PATH_REJECT_TRAILING_DOT)); +} + +void test_path_core__isvalid_trailing_space(void) +{ + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo ", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo ", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/bar ", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, " ", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo /bar", 0)); + + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo ", GIT_PATH_REJECT_TRAILING_SPACE)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo ", GIT_PATH_REJECT_TRAILING_SPACE)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/bar ", GIT_PATH_REJECT_TRAILING_SPACE)); + cl_assert_equal_b(false, git_path_isvalid(NULL, " ", GIT_PATH_REJECT_TRAILING_SPACE)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo /bar", GIT_PATH_REJECT_TRAILING_SPACE)); +} + +void test_path_core__isvalid_trailing_colon(void) +{ + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo:", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/bar:", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, ":", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "foo:/bar", 0)); + + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo:", GIT_PATH_REJECT_TRAILING_COLON)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/bar:", GIT_PATH_REJECT_TRAILING_COLON)); + cl_assert_equal_b(false, git_path_isvalid(NULL, ":", GIT_PATH_REJECT_TRAILING_COLON)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo:/bar", GIT_PATH_REJECT_TRAILING_COLON)); +} + +void test_path_core__isvalid_dos_git_shortname(void) +{ + cl_assert_equal_b(true, git_path_isvalid(NULL, "git~1", 0)); + + cl_assert_equal_b(false, git_path_isvalid(NULL, "git~1", GIT_PATH_REJECT_DOS_GIT_SHORTNAME)); +} + +void test_path_core__isvalid_dos_paths(void) +{ + cl_assert_equal_b(true, git_path_isvalid(NULL, "aux", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "aux.", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "aux:", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "aux.asdf", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "aux.asdf\\zippy", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "aux:asdf\\foobar", 0)); + + cl_assert_equal_b(false, git_path_isvalid(NULL, "aux", GIT_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "aux.", GIT_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "aux:", GIT_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "aux.asdf", GIT_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "aux.asdf\\zippy", GIT_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "aux:asdf\\foobar", GIT_PATH_REJECT_DOS_PATHS)); + + cl_assert_equal_b(true, git_path_isvalid(NULL, "aux1", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "aux1", GIT_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "auxn", GIT_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "aux\\foo", GIT_PATH_REJECT_DOS_PATHS)); +} + +void test_path_core__isvalid_dos_paths_withnum(void) +{ + cl_assert_equal_b(true, git_path_isvalid(NULL, "com1", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "com1.", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "com1:", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "com1.asdf", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "com1.asdf\\zippy", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "com1:asdf\\foobar", 0)); + + cl_assert_equal_b(false, git_path_isvalid(NULL, "com1", GIT_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "com1.", GIT_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "com1:", GIT_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "com1.asdf", GIT_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "com1.asdf\\zippy", GIT_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "com1:asdf\\foobar", GIT_PATH_REJECT_DOS_PATHS)); + + cl_assert_equal_b(true, git_path_isvalid(NULL, "com10", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "com10", GIT_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "comn", GIT_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "com1\\foo", GIT_PATH_REJECT_DOS_PATHS)); +} + +void test_core_path__isvalid_nt_chars(void) +{ + cl_assert_equal_b(true, git_path_isvalid(NULL, "asdf\001foo", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "asdf\037bar", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "asdffoo", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "asdf:foo", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "asdf\"bar", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "asdf|foo", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "asdf?bar", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "asdf*bar", 0)); + + cl_assert_equal_b(false, git_path_isvalid(NULL, "asdf\001foo", GIT_PATH_REJECT_NT_CHARS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "asdf\037bar", GIT_PATH_REJECT_NT_CHARS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "asdffoo", GIT_PATH_REJECT_NT_CHARS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "asdf:foo", GIT_PATH_REJECT_NT_CHARS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "asdf\"bar", GIT_PATH_REJECT_NT_CHARS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "asdf|foo", GIT_PATH_REJECT_NT_CHARS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "asdf?bar", GIT_PATH_REJECT_NT_CHARS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "asdf*bar", GIT_PATH_REJECT_NT_CHARS)); +} diff --git a/tests/path/win32.c b/tests/path/win32.c index ef0b5d2f2..22742f82d 100644 --- a/tests/path/win32.c +++ b/tests/path/win32.c @@ -188,3 +188,27 @@ void test_path_win32__canonicalize(void) test_canonicalize(L"\\\\server\\..\\..\\share\\.\\foo", L"\\\\server\\share\\foo"); #endif } + +void test_path_win32__8dot3_name(void) +{ +#ifdef GIT_WIN32 + char *shortname; + + /* Some guaranteed short names */ + cl_assert_equal_s("PROGRA~1", (shortname = git_win32_path_8dot3_name("C:\\Program Files"))); + git__free(shortname); + + cl_assert_equal_s("WINDOWS", (shortname = git_win32_path_8dot3_name("C:\\WINDOWS"))); + git__free(shortname); + + /* Create some predictible short names */ + cl_must_pass(p_mkdir(".foo", 0777)); + cl_assert_equal_s("FOO~1", (shortname = git_win32_path_8dot3_name(".foo"))); + git__free(shortname); + + cl_git_write2file("bar~1", "foobar\n", 7, O_RDWR|O_CREAT, 0666); + cl_must_pass(p_mkdir(".bar", 0777)); + cl_assert_equal_s("BAR~2", (shortname = git_win32_path_8dot3_name(".bar"))); + git__free(shortname); +#endif +} diff --git a/tests/resources/nasty/.gitted/HEAD b/tests/resources/nasty/.gitted/HEAD new file mode 100644 index 0000000000000000000000000000000000000000..cb089cd89a7d7686d284d8761201649346b5aa1c GIT binary patch literal 23 ecmXR)O|w!cN=+-)&qz&7Db~+TEG|hc;sO9;xClW2 literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/index b/tests/resources/nasty/.gitted/index new file mode 100644 index 0000000000000000000000000000000000000000..782a50d0a8a2df69bda7abb381350bb366e11cb6 GIT binary patch literal 120 zcmZ?q402{*U|<4bhL9jvS0EL@V5nfo)#Rad=~?sJKO*1nna=Z{71uFgPGDMoK3Hy= z{e1@O?`qN$Lywm99+(pBxouvCo*q!jfQzf*317$=cNrGZQ)~^P{3?1C&W~RFJrV6^ Tqu_LRtD)6HX4~A=uCiGG7NjaL literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/02/28b21d477f67b9f7720565da9e760b84c8b85b b/tests/resources/nasty/.gitted/objects/02/28b21d477f67b9f7720565da9e760b84c8b85b new file mode 100644 index 0000000000000000000000000000000000000000..e7cd63a28060e09aeef4f9b6ca5f950eebf2f5fb GIT binary patch literal 133 zcmV;00DAv;0i}&y3IZV%g?n}tcLNPxe@BSur4`f$9KB+YBREF1`z+c)Z|8g;oD*s( zw+`s!t~VlxLE4}bYJq97CJ_eo!7eCqw#i9nymmra<{v%R2Iu(nEy6X|vegw1H0URt n=qrA03bh;o6&k0+Y!vLd=9~>W&O7}%!!CEf<=6BE^^!pKl+QeJ literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/04/18f28a75dc0c4951c01842e0d794843a88178a b/tests/resources/nasty/.gitted/objects/04/18f28a75dc0c4951c01842e0d794843a88178a new file mode 100644 index 0000000000000000000000000000000000000000..7f8722e78fa65496a07231a18d8213f150244085 GIT binary patch literal 46 zcmbPA<=i?PL)e}BX+Mg4cr?dOt8S@{k J3})>D^#B9<6#f7J literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/09/9ed86cb8501ae483b1855c351fe1a506ac9631 b/tests/resources/nasty/.gitted/objects/09/9ed86cb8501ae483b1855c351fe1a506ac9631 new file mode 100644 index 0000000000000000000000000000000000000000..7738fc85d6f2322a3e51b1db4a99da20eeb7a13c GIT binary patch literal 133 zcmV;00DAv;0i}&g3IZ_@L|x|;eF2AbeojEdjVI6xv`L2uW&)XrczdIG19w&LK~<=w z+&ZAwo8FjV4!k4H$@-w9lZvGBPAlZAcShlybhIiF3wrci8=T^kT7*lkWvvVBdC*Tf n@>l%Y6lysD8g$;^j1IQaN+|{%=bitY5$8Lt`8B-(1lB4V=y!@Ff%bxNXyUH*VEJ2bN39<2eOkAix?VK{PPz`$_d!K U%`G2xeA<6e&uJL!>!bB~?-woKAjbW36Wv{@HVFaE>qEB3yGVYkk0x2mQE( mzr`<2p_UUMp>d?h5$vUwQVcrIJO4Q&E_c7?ZF&Q^89=!Oy*lgw literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/1e/3c845808fa5883aa4bcf2f882172edb72a7a32 b/tests/resources/nasty/.gitted/objects/1e/3c845808fa5883aa4bcf2f882172edb72a7a32 new file mode 100644 index 0000000000000000000000000000000000000000..e25f153f4c17e66a0d6ad16c70e2a6a6431cd5e4 GIT binary patch literal 134 zcmV;10D1p-0i}&m4#FT1ME&LzdjXqeVQEQ>@y8SB1t`#9A|;_13L}ZmVZbF=cT0Bot5!2{Mdf^@|l?vzZbTz;=mAsV|4zbfu oI>oQ}wZWHs1gWL5S{aM5M@AH#_VX71oDrA1+tO=z1Djkxd~;1eaR2}S literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/24/676d5e93f9fa7b568f38d7bce01772908e982b b/tests/resources/nasty/.gitted/objects/24/676d5e93f9fa7b568f38d7bce01772908e982b new file mode 100644 index 0000000000000000000000000000000000000000..fc11c20d5f9f26ef56d54e610a92a2e20c74b2d7 GIT binary patch literal 47 zcmbL=5ib54O)5VqnO?#A<8HCf4a6yR^K*hMC{P!ghz<(xa=K_i!_unaa}+01tHy A+5i9m literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/39/fb3af508440cf970b92767f6d081c811574d2a b/tests/resources/nasty/.gitted/objects/39/fb3af508440cf970b92767f6d081c811574d2a new file mode 100644 index 0000000000000000000000000000000000000000..3854748c9f3aab8554e72fa01f23493cc2fc2ce6 GIT binary patch literal 131 zcmV-}0DS*=0i}&S3PLdugspRm?FGrtCaxf2BVzw95VQMu7I7g)yuMMqfm6+V12aWy zy>)OqT=gabk(~(69_N&}aPm1=j}(bG1hSZ7x_Y|}CO`Y44Nm;ZjbU1}KJ*TI8T8|h l@-2R8Q?wqyp-V*8qKCB|GJ}ruE`QD#)(+>(WZurCKkovEJ{JH0 literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/44/14ac920acabc3eb00e3cf9375eeb0cb6859c15 b/tests/resources/nasty/.gitted/objects/44/14ac920acabc3eb00e3cf9375eeb0cb6859c15 new file mode 100644 index 0000000000000000000000000000000000000000..4eaaa0cd7485dc7c1a43e7338a61d74f2771f3f0 GIT binary patch literal 135 zcmV;20C@j+0i{n%3c@fHbe&VYy-@OR6%jYyzze**yx1<%KpOG-rs55p)qG%vp_22` zKudjSl^Hy#9(uB?T1tYICV@k8i9I9&^cCS>eLn-Pw)5 p!A}iT@)eX-<7h`USvblL(cM1Y_|Fc;Ox`l?vzZbTz;=mAsV|4&3P{ no%kz$ZSW-@fh1`=X^A83Q6Y*>`+4I(XT;_1w)7g_ioHS2YimJT literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/53/41a7b545d71198b076b8ba3374a75c9a290640 b/tests/resources/nasty/.gitted/objects/53/41a7b545d71198b076b8ba3374a75c9a290640 new file mode 100644 index 0000000000000000000000000000000000000000..fdfe6eb374957fa35e5b1dfa8d74e7ea6a944f69 GIT binary patch literal 133 zcmV;00DAv;0i}&y3c@fDgniB__5zmOq-hfn@x>G91@f~cf@vU)h_^S2H}Gxd8~7%a zoNo=ts$Hwh;EkHC_Qc3ukBgMcjCvxn(UX!IF=B`TwQ!G?N`-TLx*Fk{O5Vx}2k!Kf nPW&ByZ3rbFfkv9F(ncECV^@hz`+4I(HR5u2TY3#|w`M@8Bpg9{ literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/5d/1ee4f24f66dcd62a30248588d33804656b2073 b/tests/resources/nasty/.gitted/objects/5d/1ee4f24f66dcd62a30248588d33804656b2073 new file mode 100644 index 0000000000000000000000000000000000000000..ffd9bfd3619b9858286ed58ac02da4b464bb594c GIT binary patch literal 46 zcmb4 literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/65/94bdbad86bbc8d3ed0806a23827203fbab56c6 b/tests/resources/nasty/.gitted/objects/65/94bdbad86bbc8d3ed0806a23827203fbab56c6 new file mode 100644 index 0000000000000000000000000000000000000000..fa990d4084cc27cd6c0c50b0b3be8a6717ecaf40 GIT binary patch literal 132 zcmV-~0DJ#<0i}&m3PK?eMf-LYvw(N_OL~Zs0JP9UH+U=r(0O_YkC81n?OH^8$NXa literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/68/e8bce48725490c376d57ebc60f0170605951a5 b/tests/resources/nasty/.gitted/objects/68/e8bce48725490c376d57ebc60f0170605951a5 new file mode 100644 index 0000000000000000000000000000000000000000..c23f81597f2701fd30861cfbe6ed3ad1af8d9229 GIT binary patch literal 58 zcmV-A0LA}!0V^p=O;s>4WH2-^Ff%bx(9_p*_YBcb%g;|rEMjO_@y}l%DJNj_lD}+G QZY;b0%s2Q20B-3Kyp>uQ@Bjb+ literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/69/7dc3d723a018538eb819d5db2035c15109af73 b/tests/resources/nasty/.gitted/objects/69/7dc3d723a018538eb819d5db2035c15109af73 new file mode 100644 index 0000000000000000000000000000000000000000..6d7d9f5007a1c29cd55fc1343cfb48c943fca01f GIT binary patch literal 132 zcmV-~0DJ#<0i}&g3IZ_@L|x|;eF29glaCD|Zajfrpp$fnU<}Me#M>Li8@Q|Y3aVnO z_1ZzN literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/6c/1f5f6fec515d33036b44c596bfae28fc460cba b/tests/resources/nasty/.gitted/objects/6c/1f5f6fec515d33036b44c596bfae28fc460cba new file mode 100644 index 0000000000000000000000000000000000000000..8172b7f0a10cc26a4b05d5a61d6a9a5e890b48a7 GIT binary patch literal 47 zcmbL=5ib54OKd DYXK1< literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/7d/4e382485ace068fb83b768ba1a1c674afbdc1d b/tests/resources/nasty/.gitted/objects/7d/4e382485ace068fb83b768ba1a1c674afbdc1d new file mode 100644 index 0000000000000000000000000000000000000000..f7be9ab39c6fe4d95b63d3eb2e64855b5c3050e0 GIT binary patch literal 62 zcmV-E0Kxxw0V^p=O;s>4V=y!@Ff%bxNXyUH*VEJ2OV2FP2eOkAix?VK{PPz`$_d!K UL=5ib54OUJuxA|~c`igTboK=oDL;V#sX0^t literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/8b/cbb6e0c0f9554efd5401e1ec14a4b2595eb3bf b/tests/resources/nasty/.gitted/objects/8b/cbb6e0c0f9554efd5401e1ec14a4b2595eb3bf new file mode 100644 index 0000000000000000000000000000000000000000..bba2035da7164a2bcafa8dc5fb8c2a5173791dd7 GIT binary patch literal 134 zcmV;10D1p-0i}&y3IZV%g?n}tcLNPxe@2CfURptIz|kuPIf7$EyU(H>^mfko!8xIp za_fLj?s_AF6pfcAI)ToJ*^t(RDew?f5>|SPdWps@mib4|wZS<)eT#6-wQO~T0}c8~ oC;EzCn?fx|K!vkYQYr&`t~qCej`L1`&alhfZ}~O70S|*gDTd!Y3IG5A literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/8c/e7a3ef59c3d602a0296321eb964218f3d52fae b/tests/resources/nasty/.gitted/objects/8c/e7a3ef59c3d602a0296321eb964218f3d52fae new file mode 100644 index 0000000000000000000000000000000000000000..6f3484c1ae520cbb98c6625cf622beb31a102b47 GIT binary patch literal 56 zcmV-80LTA$0V^p=O;s?qWH2-^Ff%bx&`ZxOiAl@PPf9FeXjt*jUmz(bVDpl{Y*B73 OyZ+2K_yquL{t@$0lNXl& literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/8f/1dcd43aa0164eb6ec319c3ec8879ca5cf62c1e b/tests/resources/nasty/.gitted/objects/8f/1dcd43aa0164eb6ec319c3ec8879ca5cf62c1e new file mode 100644 index 0000000000000000000000000000000000000000..f802e5af7eea5a9ae7d67da7d15f8b4044147bfd GIT binary patch literal 132 zcmV-~0DJ#<0i}&g3IZ_@L|x|;eF2AjI%7A8xbXyffsk~FU<}Me#M>Li8@Q`_52|9T z_1eL(+w?^Q$RgR0tjy#jU;}pc};H^20<@BB|yCZ literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/91/602c85bb50dd834205edd30435b77d5bb9ccf0 b/tests/resources/nasty/.gitted/objects/91/602c85bb50dd834205edd30435b77d5bb9ccf0 new file mode 100644 index 0000000000000000000000000000000000000000..d7147fb1c0bd783d457b251cce9567e20d260ab7 GIT binary patch literal 131 zcmV-}0DS*=0i}&m3IZ_eD z=W7EBcGIdV#Az4Qql%NTIBUswU>%9Q7n*qyq9`F4eza67oaD(>;F3yS%N_RG?I#`e lTl{EHu`1@h?-!5GLy#M>Li8@Q`_1+PLa z<=R17wdsu+IBFk|R9a*VO3x`F?zFc=p2$YhkqH+z!aaJf4NmdtT7*lkWvzGE^Prz} mf(tQcE+`Xb`qIBZ@)CdFMYh;(T*!eob%rI6&s&yFCg3 literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/96/3fdf003bf7261b9155c5748dc0945349b69e68 b/tests/resources/nasty/.gitted/objects/96/3fdf003bf7261b9155c5748dc0945349b69e68 new file mode 100644 index 0000000000000000000000000000000000000000..ff1d33e5cc4aecbb998f62863ebf782f13eb0878 GIT binary patch literal 44 zcmb)5VqnO?#A<8HwysOPMlt2CtPDKy G{O$nMpA)tK literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/af/45aa1eb7edf804ed10f70efb96fd178527c17c b/tests/resources/nasty/.gitted/objects/af/45aa1eb7edf804ed10f70efb96fd178527c17c new file mode 100644 index 0000000000000000000000000000000000000000..9e270bfbc9eeeed975756efcb5aac58b4b8540df GIT binary patch literal 58 zcmV-A0LA}!0V^p=O;s>4WH2-^Ff%bx(9_pT&n(eT%g;|rEMjO_@y}l%DJNj_lD}+G QZY;b0%s2Q20D37B9E)@pZ2$lO literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/b8/3795b1e0eb54f22f7056119db132500d0cdc05 b/tests/resources/nasty/.gitted/objects/b8/3795b1e0eb54f22f7056119db132500d0cdc05 new file mode 100644 index 0000000000000000000000000000000000000000..6cee4f9d823d3ff9160feaa143681c6d564f3c69 GIT binary patch literal 56 zcmV-80LTA$0V^p=O;s?qWH2-^Ff%bx&`ZxO(ND|IPf9FeXjt*jUmz(bVDpl{Y*B73 OyZ+2K_yquH5)sB(HWtVL literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/bf/7ab4723fcc57ecc7fceccf591d6c4773491569 b/tests/resources/nasty/.gitted/objects/bf/7ab4723fcc57ecc7fceccf591d6c4773491569 new file mode 100644 index 0000000000000000000000000000000000000000..af02c6b9b67d7189022a8e79c1dcbc031ec6f080 GIT binary patch literal 133 zcmV;00DAv;0i}&g3IZ_@L|x|;eF2B=d>jKJZajfrV5U1Ef|)=jBHrF8-oRbedr-wC z=UW5vX47g2fR42GbM#^`u-68ij?rqF_qg-S!ZeceqNYbnrNUXBQx&+TlGn1pA$0ml nr|=cOHgL&DAWI{X)5gLU9ir;ApSSSmjJn*XHNA#6)ptP056DAD literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/c4/89e70ed6d9f6331770eae21a77d15afd11cd99 b/tests/resources/nasty/.gitted/objects/c4/89e70ed6d9f6331770eae21a77d15afd11cd99 new file mode 100644 index 0000000000000000000000000000000000000000..1d763482f01fb9ba80823f8708df898a0c334a9f GIT binary patch literal 56 zcmV-80LTA$0V^p=O;s>4WH2-^Ff%bxNXyUH*VEGnQb~zL3=J#(`3ofF1Z-aNmo3VT OW!Ini2EPD&SrQycd=_K? literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/c6/72414d4d08111145ef8202f21c95fa7e688aee b/tests/resources/nasty/.gitted/objects/c6/72414d4d08111145ef8202f21c95fa7e688aee new file mode 100644 index 0000000000000000000000000000000000000000..1b79b342c36e06fc2ca5638bd5a84e0b9cbea7fc GIT binary patch literal 56 zcmV-80LTA$0V^p=O;s?qWH2-^Ff%bx&~x_;iAl@PPf9FeXjt*jUmz(bVDpl{Y*B73 OyZ+2K_yquAz7dmGe-|78 literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/cc/bbfdb796f9b03298f5c7225e8f830784e1a3b1 b/tests/resources/nasty/.gitted/objects/cc/bbfdb796f9b03298f5c7225e8f830784e1a3b1 new file mode 100644 index 0000000000000000000000000000000000000000..732474aefdf129bcab450d44bf357a34a52be156 GIT binary patch literal 136 zcmV;30C)d*0i{n#3IZ_@^qg1pT^O=-j3Xjm{DB|nB&{=tOhYE(_sxhuuuio=6_uov zxq;Pw*D5nGa*pm`*qhPW=u_%C1!go6vm>;}kvOrUSDU25Ex)N|xKC16S>Vjw-Po1C q!A}iI$^{IX5HMJcu-6@`yM4UzpEYWD&h7LN0a}4z`=LJGn?x-Rt^#q>L=5ib54OfK~jkhSRGl7(`$eQL9Jrf0+_(#vR!8tyCi*U`gtaX6{5Bf4WH2-^Ff%bx(2LP?_Y8?i%g;|rEMjO_@y}l%DJNj_lD}+G QZY;b0%s2Q20C}Pk7JWY#a{vGU literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/de/bdc4a004fda6141a17d9c297617be70d40248f b/tests/resources/nasty/.gitted/objects/de/bdc4a004fda6141a17d9c297617be70d40248f new file mode 100644 index 0000000000000000000000000000000000000000..de34bd4305ef86f32ab42e7cca56eda0427cb12b GIT binary patch literal 133 zcmV;00DAv;0i}&g3IZ_@L|x|;eF2AbW+opHapMW}0&S?Ji&zt-?qfR$km)GzHZO}oaNn<+< literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/e2/377bdbc93b30a34ed5deefedded89b947ff8f4 b/tests/resources/nasty/.gitted/objects/e2/377bdbc93b30a34ed5deefedded89b947ff8f4 new file mode 100644 index 0000000000000000000000000000000000000000..f365908e0f3df2e46ab8ce03383781a4b588c4da GIT binary patch literal 132 zcmV-~0DJ#<0i}&g3IZ_@L|x|;eF2B=$sa`AcmlmZCTWRa3}hnW?Tz9M+*Q2?Rk4bBw>phQ4BiHJO4Q&PPee;*YpOE(Lh&e20u^$ literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/e3/99c4fc4c07cb7947d2f3d966bc374df6ccc691 b/tests/resources/nasty/.gitted/objects/e3/99c4fc4c07cb7947d2f3d966bc374df6ccc691 new file mode 100644 index 0000000000000000000000000000000000000000..d8c2379461f2fecb2ba973f1a942c7a8c465d1b5 GIT binary patch literal 131 zcmV-}0DS*=0i}&m3IZ_;x3?bc(H8N2Oia$_-LtAI4;jv7dWSq*K&thJN=}C le#Ng1Qt}>%$s>gc8Mf#UO{e|5>7O&^bc<_x4R3pxKsb}+K;{4d literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/e4/edb361e51932b5ccedbc7ee41b4d3a4289aece b/tests/resources/nasty/.gitted/objects/e4/edb361e51932b5ccedbc7ee41b4d3a4289aece new file mode 100644 index 0000000000000000000000000000000000000000..a9b181815a9e29bb5000680fac07fc49e1ae4688 GIT binary patch literal 50 zcmbL=5ib54O)5U|`6=#A?g7u1mc}G5tLp)W+k5y8MR|-ARA3E6QtJC z0ThO??5$ra+g%9zYZT literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/fd/7a37d92197267e55e1fc0cc4f283a815bd79b8 b/tests/resources/nasty/.gitted/objects/fd/7a37d92197267e55e1fc0cc4f283a815bd79b8 new file mode 100644 index 0000000000000000000000000000000000000000..c8d38ca46c6e7471571bd7154436210c82f3e126 GIT binary patch literal 43 zcmb)5U|`6=#A?eX*6AO+w7kNGncu_0c8A^4qpO_va5Efb<%s|Q0-+4k literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/dot_backslash_dotcapitalgit_path b/tests/resources/nasty/.gitted/refs/heads/dot_backslash_dotcapitalgit_path new file mode 100644 index 0000000000000000000000000000000000000000..06132bc80f2dcdb2067cf9f3390bc0772c0e1710 GIT binary patch literal 41 ucmV~$!4Uu;2m`Rc(;x>F#<3#%k6#<5uTAHn3X$So`D1Va4~=5m$C4NfG)#U3C0>I<;| literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/dot_git_colon b/tests/resources/nasty/.gitted/refs/heads/dot_git_colon new file mode 100644 index 0000000000000000000000000000000000000000..39052d99a592f17279df233ee8d33ae5e9be4b00 GIT binary patch literal 41 ucmXppF*Hd`wlp$GOioNnHcm}4Fi15{PO~&NH%$d{l9S9VOf8cQO}PN}stbAm literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/dot_git_colon_stuff b/tests/resources/nasty/.gitted/refs/heads/dot_git_colon_stuff new file mode 100644 index 0000000000000000000000000000000000000000..a3bc39f66f069a26304e10049b7e39aaaf80ec4b GIT binary patch literal 41 vcmV~$NdW*L2n4{tX*fh6j*HMgf-_aXJb0c-kV?i_xrlX4D0%GWtKjtk{(TEN literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/dot_git_dot b/tests/resources/nasty/.gitted/refs/heads/dot_git_dot new file mode 100644 index 0000000000000000000000000000000000000000..b20a1e0acf4539fcb5a90e84602e31153b538bc9 GIT binary patch literal 41 ucmV~$K>+|D2m`>sX%u0TIDpbWf_H)htQMrfsCXdhs@A)h&LcSkm*WHC@(Qy6 literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/dot_path b/tests/resources/nasty/.gitted/refs/heads/dot_path new file mode 100644 index 0000000000000000000000000000000000000000..b3c7ab682ae122c52d845267fa4455ba2513c1a9 GIT binary patch literal 41 ucmYdFGfzx1F*hs?3r{pPM=-JW%SdjHl5{D~9SjL8^g9KW9P0z=y9)jQ literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/dotdot_path b/tests/resources/nasty/.gitted/refs/heads/dotdot_path new file mode 100644 index 0000000000000000000000000000000000000000..185e13b11d9970cda1eebc756a720c26a8eda260 GIT binary patch literal 41 vcmcCCG&3+twlGafGBrp^u`o6+|D2m`>sX+#hq4$jd(f_KsK@Jy?wxbZrnl29VzBx<95K{!79RSUcT literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/dotgit_backslash_path b/tests/resources/nasty/.gitted/refs/heads/dotgit_backslash_path new file mode 100644 index 0000000000000000000000000000000000000000..6e4344dd6f24ab919b13c76a45d92ebc2d934951 GIT binary patch literal 41 ucmV~$!4Uu;2m`Rc)6m0mWgJA@e*_cDoU7;~8fv!uKx8}d21SeN49EKB7YdvJ literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/dotgit_path b/tests/resources/nasty/.gitted/refs/heads/dotgit_path new file mode 100644 index 0000000000000000000000000000000000000000..dd71efaa4859bd96448a6680857cadb10877be57 GIT binary patch literal 41 tcmV~$K>+|D2m`>sX+Sn6aX|ZL@Qwj*4Z$b(I%CQ?(-A_8w|bW)Y<<^#3JL%K literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/dotgit_tree b/tests/resources/nasty/.gitted/refs/heads/dotgit_tree new file mode 100644 index 0000000000000000000000000000000000000000..3b7a08d7ced193fb2b4ba234cc9c3d3d0fa5630d GIT binary patch literal 41 ucmV~$!4Uu;2m`Rc({P}*jzdBBAHf9cOtrEJsn~JbXh;wGc8jOfLScOOa|>1g literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/git_tilde1 b/tests/resources/nasty/.gitted/refs/heads/git_tilde1 new file mode 100644 index 0000000000000000000000000000000000000000..d48a185304c03a5199ad0f06978b600abc0ff303 GIT binary patch literal 41 ucmcCCNi#N2HnKD{H%_)lOEfV}G%_|yGBi&$H!)4NFfs&kjM5U*Qn&!$xeChw literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/git_tilde2 b/tests/resources/nasty/.gitted/refs/heads/git_tilde2 new file mode 100644 index 0000000000000000000000000000000000000000..77082e153322919b5078a735698428159f91a6ab GIT binary patch literal 41 tcmV~$$pHW$2m`Rc;~-Evj$HeXU=rl61XOd{a%!A~$On@`*BzR=@O<~Z3s(RD literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/git_tilde3 b/tests/resources/nasty/.gitted/refs/heads/git_tilde3 new file mode 100644 index 0000000000000000000000000000000000000000..73022aad6833621762a0ac2aa388b882f1aa619f GIT binary patch literal 41 ucmV~$K>+|D2m`>sZKj|SXMpP;!8 Date: Tue, 2 Dec 2014 22:20:42 -0500 Subject: [PATCH 07/11] reference_create: validate loose names Validate loose reference names on Win32. --- src/refdb_fs.c | 2 +- tests/refs/create.c | 48 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/refdb_fs.c b/src/refdb_fs.c index 61d1cd56e..fc41a95d7 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -707,7 +707,7 @@ static int reference_path_available( static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *name) { - int error; + int error; git_buf ref_path = GIT_BUF_INIT; assert(file && backend && name); diff --git a/tests/refs/create.c b/tests/refs/create.c index 8e4d8d70b..3af7c1d15 100644 --- a/tests/refs/create.c +++ b/tests/refs/create.c @@ -151,13 +151,11 @@ void test_refs_create__propagate_eexists(void) cl_assert(error == GIT_EEXISTS); } -void test_refs_create__creating_a_reference_with_an_invalid_name_returns_EINVALIDSPEC(void) +static void test_invalid_name(const char *name) { git_reference *new_reference; git_oid id; - const char *name = "refs/heads/inv@{id"; - git_oid_fromstr(&id, current_master_tip); cl_assert_equal_i(GIT_EINVALIDSPEC, git_reference_create( @@ -166,3 +164,47 @@ void test_refs_create__creating_a_reference_with_an_invalid_name_returns_EINVALI cl_assert_equal_i(GIT_EINVALIDSPEC, git_reference_symbolic_create( &new_reference, g_repo, name, current_head_target, 0, NULL, NULL)); } + +void test_refs_create__creating_a_reference_with_an_invalid_name_returns_EINVALIDSPEC(void) +{ + test_invalid_name("refs/heads/inv@{id"); + test_invalid_name("refs/heads/back\\slash"); + + test_invalid_name("refs/heads/foo "); + test_invalid_name("refs/heads/foo /bar"); + test_invalid_name("refs/heads/com1:bar/foo"); + + test_invalid_name("refs/heads/e:"); + test_invalid_name("refs/heads/c:/foo"); + + test_invalid_name("refs/heads/foo."); +} + +static void test_win32_name(const char *name) +{ + git_reference *new_reference = NULL; + git_oid id; + int ret; + + git_oid_fromstr(&id, current_master_tip); + + ret = git_reference_create(&new_reference, g_repo, name, &id, 0, NULL, NULL); + +#ifdef GIT_WIN32 + cl_assert_equal_i(GIT_EINVALIDSPEC, ret); +#else + cl_git_pass(ret); +#endif + + git_reference_free(new_reference); +} + +void test_refs_create__creating_a_loose_ref_with_invalid_windows_name(void) +{ + test_win32_name("refs/heads/foo./bar"); + + test_win32_name("refs/heads/aux"); + test_win32_name("refs/heads/aux.foo/bar"); + + test_win32_name("refs/heads/com1"); +} From 11d67b754d47967642570f796601e8850f001d73 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 10 Dec 2014 19:12:16 -0500 Subject: [PATCH 08/11] checkout: disallow bad paths on HFS HFS filesystems ignore some characters like U+200C. When these characters are included in a path, they will be ignored for the purposes of comparison with other paths. Thus, if you have a ".git" folder, a folder of ".git" will also match. Protect our ".git" folder by ensuring that ".git" and friends do not match it. --- src/path.c | 93 ++++++++++++++++++ src/path.h | 5 + tests/checkout/nasty.c | 42 ++++++++ tests/path/core.c | 31 +++++- .../04/fab819d8388295cbe3496310e4e53ef8f4a115 | Bin 0 -> 49 bytes .../0b/8206dd72a3b3b932fb562f92d29199b9398390 | Bin 0 -> 50 bytes .../10/cb44a89d1a9e8bf74de3f11a2a61ee833f13b1 | Bin 0 -> 50 bytes .../11/9f6cd3535de0e2a15654947a7b1a5affbf1406 | Bin 0 -> 50 bytes .../15/f7d9f9514eeb65b9588c49b10b1da145a729a2 | Bin 0 -> 137 bytes .../16/35c47d80914f0abfa43dd4234a948db5bdb107 | Bin 0 -> 137 bytes .../2b/4b774d8c5441b22786531f34ffc77800cda8cf | Bin 0 -> 50 bytes .../2d/23d51590ec2f53fe4b5bb3e5ca62e35e4ef85a | Bin 0 -> 49 bytes .../35/ae236308929a536fb4e852278a9b98c42babb3 | Bin 0 -> 137 bytes .../38/0b9e58872ccf1d858be4b0fc612514a080bc40 | Bin 0 -> 49 bytes .../3b/24e5c751ee9c7c89df32a0d959748aa3d0112c | Bin 0 -> 136 bytes .../44/2894787eddb1e84a952f17a027590e2c6c02cd | Bin 0 -> 137 bytes .../46/fe10fa23259b089ab050788b06df979cd7d054 | Bin 0 -> 137 bytes .../6b/7d8a5a48a3c753b75a8fe5196f9c8704ac64ad | Bin 0 -> 50 bytes .../71/2ceb8eb3e57072447715bc4057c57aa50f629a | Bin 0 -> 138 bytes .../7a/0538bc4e20aecb36ef221f2077eb30ebe0bcb2 | Bin 0 -> 136 bytes .../7a/e174dda8f105a582c593b52d74545a3565819d | Bin 0 -> 51 bytes .../80/24458e7ee49c456fd8c45d3591e9936bf613b3 | Bin 0 -> 50 bytes .../81/e2b84864f16ebd285b34a2b1e87ebb41f4c230 | Bin 0 -> 49 bytes .../88/6c0f5f71057d846f71f05a05fdffad332bc070 | Bin 0 -> 50 bytes .../96/156716851c0afb4702b0d2c4ac8c496a730e29 | Bin 0 -> 137 bytes .../9a/b85e507899c19dca57778c9b6e5f1ec799b911 | Bin 0 -> 135 bytes .../9e/24726d64589ba02430da8cebb5712dad35593d | Bin 0 -> 136 bytes .../a5/76a98d3279989226992610372035b76a01a3e9 | Bin 0 -> 136 bytes .../b1/1df9aee97a65817e8904a74f5e6a1c62c7a275 | Bin 0 -> 50 bytes .../bb/29ec85546d29b0bcc314242660d7772b0a3803 | Bin 0 -> 50 bytes .../c2/a2ddd339574e5cbfd9228be840eb1bf496de4e | Bin 0 -> 137 bytes .../c3/a70f8a376f17adccfb52b48e2831bfef2a2172 | Bin 0 -> 136 bytes .../c8/f98a1762ec016c30f0d73512df399dedefc3fd | Bin 0 -> 136 bytes .../ce/22b3cd9a01efafc370879c1938e0c32fb6f195 | Bin 0 -> 136 bytes .../e7/3a04f71f11ab9d7dde72ff793882757a03f16e | Bin 0 -> 50 bytes .../eb/82bf596b66f90e25f881ce9b92cb55bab4fdf5 | Bin 0 -> 50 bytes .../f2/c059dab35f6534b3f16d90b2f1de308615320c | Bin 0 -> 50 bytes .../.gitted/refs/heads/dotgit_hfs_ignorable_1 | Bin 0 -> 41 bytes .../refs/heads/dotgit_hfs_ignorable_10 | Bin 0 -> 41 bytes .../refs/heads/dotgit_hfs_ignorable_11 | Bin 0 -> 41 bytes .../refs/heads/dotgit_hfs_ignorable_12 | Bin 0 -> 41 bytes .../refs/heads/dotgit_hfs_ignorable_13 | Bin 0 -> 41 bytes .../refs/heads/dotgit_hfs_ignorable_14 | Bin 0 -> 41 bytes .../refs/heads/dotgit_hfs_ignorable_15 | Bin 0 -> 41 bytes .../refs/heads/dotgit_hfs_ignorable_16 | Bin 0 -> 41 bytes .../.gitted/refs/heads/dotgit_hfs_ignorable_2 | Bin 0 -> 41 bytes .../.gitted/refs/heads/dotgit_hfs_ignorable_3 | Bin 0 -> 41 bytes .../.gitted/refs/heads/dotgit_hfs_ignorable_4 | Bin 0 -> 41 bytes .../.gitted/refs/heads/dotgit_hfs_ignorable_5 | Bin 0 -> 41 bytes .../.gitted/refs/heads/dotgit_hfs_ignorable_6 | Bin 0 -> 41 bytes .../.gitted/refs/heads/dotgit_hfs_ignorable_7 | Bin 0 -> 41 bytes .../.gitted/refs/heads/dotgit_hfs_ignorable_8 | Bin 0 -> 41 bytes .../.gitted/refs/heads/dotgit_hfs_ignorable_9 | Bin 0 -> 41 bytes 53 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 tests/resources/nasty/.gitted/objects/04/fab819d8388295cbe3496310e4e53ef8f4a115 create mode 100644 tests/resources/nasty/.gitted/objects/0b/8206dd72a3b3b932fb562f92d29199b9398390 create mode 100644 tests/resources/nasty/.gitted/objects/10/cb44a89d1a9e8bf74de3f11a2a61ee833f13b1 create mode 100644 tests/resources/nasty/.gitted/objects/11/9f6cd3535de0e2a15654947a7b1a5affbf1406 create mode 100644 tests/resources/nasty/.gitted/objects/15/f7d9f9514eeb65b9588c49b10b1da145a729a2 create mode 100644 tests/resources/nasty/.gitted/objects/16/35c47d80914f0abfa43dd4234a948db5bdb107 create mode 100644 tests/resources/nasty/.gitted/objects/2b/4b774d8c5441b22786531f34ffc77800cda8cf create mode 100644 tests/resources/nasty/.gitted/objects/2d/23d51590ec2f53fe4b5bb3e5ca62e35e4ef85a create mode 100644 tests/resources/nasty/.gitted/objects/35/ae236308929a536fb4e852278a9b98c42babb3 create mode 100644 tests/resources/nasty/.gitted/objects/38/0b9e58872ccf1d858be4b0fc612514a080bc40 create mode 100644 tests/resources/nasty/.gitted/objects/3b/24e5c751ee9c7c89df32a0d959748aa3d0112c create mode 100644 tests/resources/nasty/.gitted/objects/44/2894787eddb1e84a952f17a027590e2c6c02cd create mode 100644 tests/resources/nasty/.gitted/objects/46/fe10fa23259b089ab050788b06df979cd7d054 create mode 100644 tests/resources/nasty/.gitted/objects/6b/7d8a5a48a3c753b75a8fe5196f9c8704ac64ad create mode 100644 tests/resources/nasty/.gitted/objects/71/2ceb8eb3e57072447715bc4057c57aa50f629a create mode 100644 tests/resources/nasty/.gitted/objects/7a/0538bc4e20aecb36ef221f2077eb30ebe0bcb2 create mode 100644 tests/resources/nasty/.gitted/objects/7a/e174dda8f105a582c593b52d74545a3565819d create mode 100644 tests/resources/nasty/.gitted/objects/80/24458e7ee49c456fd8c45d3591e9936bf613b3 create mode 100644 tests/resources/nasty/.gitted/objects/81/e2b84864f16ebd285b34a2b1e87ebb41f4c230 create mode 100644 tests/resources/nasty/.gitted/objects/88/6c0f5f71057d846f71f05a05fdffad332bc070 create mode 100644 tests/resources/nasty/.gitted/objects/96/156716851c0afb4702b0d2c4ac8c496a730e29 create mode 100644 tests/resources/nasty/.gitted/objects/9a/b85e507899c19dca57778c9b6e5f1ec799b911 create mode 100644 tests/resources/nasty/.gitted/objects/9e/24726d64589ba02430da8cebb5712dad35593d create mode 100644 tests/resources/nasty/.gitted/objects/a5/76a98d3279989226992610372035b76a01a3e9 create mode 100644 tests/resources/nasty/.gitted/objects/b1/1df9aee97a65817e8904a74f5e6a1c62c7a275 create mode 100644 tests/resources/nasty/.gitted/objects/bb/29ec85546d29b0bcc314242660d7772b0a3803 create mode 100644 tests/resources/nasty/.gitted/objects/c2/a2ddd339574e5cbfd9228be840eb1bf496de4e create mode 100644 tests/resources/nasty/.gitted/objects/c3/a70f8a376f17adccfb52b48e2831bfef2a2172 create mode 100644 tests/resources/nasty/.gitted/objects/c8/f98a1762ec016c30f0d73512df399dedefc3fd create mode 100644 tests/resources/nasty/.gitted/objects/ce/22b3cd9a01efafc370879c1938e0c32fb6f195 create mode 100644 tests/resources/nasty/.gitted/objects/e7/3a04f71f11ab9d7dde72ff793882757a03f16e create mode 100644 tests/resources/nasty/.gitted/objects/eb/82bf596b66f90e25f881ce9b92cb55bab4fdf5 create mode 100644 tests/resources/nasty/.gitted/objects/f2/c059dab35f6534b3f16d90b2f1de308615320c create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_1 create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_10 create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_11 create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_12 create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_13 create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_14 create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_15 create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_16 create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_2 create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_3 create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_4 create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_5 create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_6 create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_7 create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_8 create mode 100644 tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_9 diff --git a/src/path.c b/src/path.c index dbe193acb..724d9ede2 100644 --- a/src/path.c +++ b/src/path.c @@ -1282,6 +1282,95 @@ GIT_INLINE(bool) verify_dospath( component[last] != ':'); } +GIT_INLINE(bool) verify_dotgit_hfs(const char *component, size_t len) +{ + const unsigned char *c; + int git = 0, ign = 0; + unsigned char one, two; + + while (len) { + switch (*(c = (const unsigned char *)component++)) { + case '.': + if (ign || git++ != 0) + return true; + break; + case 'g': + case 'G': + if (ign || git++ != 1) + return true; + break; + case 'i': + case 'I': + if (ign || git++ != 2) + return true; + break; + case 't': + case 'T': + if (ign || git++ != 3) + return true; + break; + + case 0xe2: + case 0xef: + if (ign++ != 0) + return true; + one = *c; + break; + + case 0x80: + case 0x81: + if (ign++ != 1 || one != 0xe2) + return true; + two = *c; + break; + + case 0xbb: + if (ign++ != 1 || one != 0xef) + return true; + two = *c; + break; + + case 0x8c: + case 0x8d: + case 0x8e: + case 0x8f: + if (ign != 2 || two != 0x80) + return true; + ign = 0; + break; + + case 0xaa: + case 0xab: + case 0xac: + case 0xad: + case 0xae: + if (ign != 2 || (two != 0x80 && two != 0x81)) + return true; + ign = 0; + break; + + case 0xaf: + if (ign != 2 || two != 0x81) + return true; + ign = 0; + break; + + case 0xbf: + if (ign != 2 || two != 0xbb) + return true; + ign = 0; + break; + + default: + return true; + } + + len--; + } + + return (ign || git != 4); +} + GIT_INLINE(bool) verify_char(unsigned char c, unsigned int flags) { if ((flags & GIT_PATH_REJECT_BACKSLASH) && c == '\\') @@ -1362,6 +1451,10 @@ static bool verify_component( return false; } + if (flags & GIT_PATH_REJECT_DOT_GIT_HFS && + !verify_dotgit_hfs(component, len)) + return false; + return true; } diff --git a/src/path.h b/src/path.h index a97668a98..4e25d9378 100644 --- a/src/path.h +++ b/src/path.h @@ -472,6 +472,7 @@ extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or #define GIT_PATH_REJECT_DOS_GIT_SHORTNAME (1 << 6) #define GIT_PATH_REJECT_DOS_PATHS (1 << 7) #define GIT_PATH_REJECT_NT_CHARS (1 << 8) +#define GIT_PATH_REJECT_DOT_GIT_HFS (1 << 9) #ifdef GIT_WIN32 # define GIT_PATH_REJECT_DEFAULTS \ @@ -483,6 +484,10 @@ extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or GIT_PATH_REJECT_DOS_GIT_SHORTNAME | \ GIT_PATH_REJECT_DOS_PATHS | \ GIT_PATH_REJECT_NT_CHARS +#elif __APPLE__ +# define GIT_PATH_REJECT_DEFAULTS \ + GIT_PATH_REJECT_TRAVERSAL | \ + GIT_PATH_REJECT_DOT_GIT_HFS #else # define GIT_PATH_REJECT_DEFAULTS GIT_PATH_REJECT_TRAVERSAL #endif diff --git a/tests/checkout/nasty.c b/tests/checkout/nasty.c index 8bc98f3d6..a667dcd8f 100644 --- a/tests/checkout/nasty.c +++ b/tests/checkout/nasty.c @@ -249,3 +249,45 @@ void test_checkout_nasty__dot_git_colon_stuff(void) #endif } +/* Trees that contains entries with a tree ".git" that contain + * byte sequences: + * { 0xe2, 0x80, 0x8c } + * { 0xe2, 0x80, 0x8d } + * { 0xe2, 0x80, 0x8e } + * { 0xe2, 0x80, 0x8f } + * { 0xe2, 0x80, 0xaa } + * { 0xe2, 0x80, 0xab } + * { 0xe2, 0x80, 0xac } + * { 0xe2, 0x80, 0xad } + * { 0xe2, 0x81, 0xae } + * { 0xe2, 0x81, 0xaa } + * { 0xe2, 0x81, 0xab } + * { 0xe2, 0x81, 0xac } + * { 0xe2, 0x81, 0xad } + * { 0xe2, 0x81, 0xae } + * { 0xe2, 0x81, 0xaf } + * { 0xef, 0xbb, 0xbf } + * Because these map to characters that HFS filesystems "ignore". Thus + * ".git" will map to ".git". + */ +void test_checkout_nasty__dot_git_hfs_ignorable(void) +{ +#ifdef __APPLE__ + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_1", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_2", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_3", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_4", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_5", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_6", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_7", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_8", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_9", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_10", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_11", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_12", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_13", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_14", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_15", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_16", ".git/foobar"); +#endif +} diff --git a/tests/path/core.c b/tests/path/core.c index 7cc111800..528108bea 100644 --- a/tests/path/core.c +++ b/tests/path/core.c @@ -223,7 +223,7 @@ void test_path_core__isvalid_dos_paths_withnum(void) cl_assert_equal_b(true, git_path_isvalid(NULL, "com1\\foo", GIT_PATH_REJECT_DOS_PATHS)); } -void test_core_path__isvalid_nt_chars(void) +void test_path_core__isvalid_nt_chars(void) { cl_assert_equal_b(true, git_path_isvalid(NULL, "asdf\001foo", 0)); cl_assert_equal_b(true, git_path_isvalid(NULL, "asdf\037bar", 0)); @@ -245,3 +245,32 @@ void test_core_path__isvalid_nt_chars(void) cl_assert_equal_b(false, git_path_isvalid(NULL, "asdf?bar", GIT_PATH_REJECT_NT_CHARS)); cl_assert_equal_b(false, git_path_isvalid(NULL, "asdf*bar", GIT_PATH_REJECT_NT_CHARS)); } + +void test_path_core__isvalid_dotgit_with_hfs_ignorables(void) +{ + cl_assert_equal_b(false, git_path_isvalid(NULL, ".git", GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, ".git\xe2\x80\x8c", GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, ".gi\xe2\x80\x8dT", GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, ".g\xe2\x80\x8eIt", GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, ".\xe2\x80\x8fgIt", GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "\xe2\x80\xaa.gIt", GIT_PATH_REJECT_DOT_GIT_HFS)); + + cl_assert_equal_b(false, git_path_isvalid(NULL, "\xe2\x80\xab.\xe2\x80\xacG\xe2\x80\xadI\xe2\x80\xaet", GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "\xe2\x81\xab.\xe2\x80\xaaG\xe2\x81\xabI\xe2\x80\xact", GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "\xe2\x81\xad.\xe2\x80\xaeG\xef\xbb\xbfIT", GIT_PATH_REJECT_DOT_GIT_HFS)); + + cl_assert_equal_b(true, git_path_isvalid(NULL, ".", GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_isvalid(NULL, ".g", GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_isvalid(NULL, ".gi", GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_isvalid(NULL, " .git", GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "..git\xe2\x80\x8c", GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_isvalid(NULL, ".gi\xe2\x80\x8dT.", GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_isvalid(NULL, ".g\xe2\x80It", GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_isvalid(NULL, ".\xe2gIt", GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "\xe2\x80\xaa.gi", GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_isvalid(NULL, ".gi\x80\x8dT", GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_isvalid(NULL, ".gi\x8dT", GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_isvalid(NULL, ".g\xe2i\x80T\x8e", GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_isvalid(NULL, ".git\xe2\x80\xbf", GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_isvalid(NULL, ".git\xe2\xab\x81", GIT_PATH_REJECT_DOT_GIT_HFS)); +} diff --git a/tests/resources/nasty/.gitted/objects/04/fab819d8388295cbe3496310e4e53ef8f4a115 b/tests/resources/nasty/.gitted/objects/04/fab819d8388295cbe3496310e4e53ef8f4a115 new file mode 100644 index 0000000000000000000000000000000000000000..688b970c28cbaf7a997e903e8127fe0c43f9952c GIT binary patch literal 49 zcmV-10M7q-0V^p=O;s>9VK6ZO0)mv^%jMCzfVjzIZaa)Z1y1Pq6fJCI+8g G!5{$3{1Zk1 literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/10/cb44a89d1a9e8bf74de3f11a2a61ee833f13b1 b/tests/resources/nasty/.gitted/objects/10/cb44a89d1a9e8bf74de3f11a2a61ee833f13b1 new file mode 100644 index 0000000000000000000000000000000000000000..9d14298d58eda98f267e3b01afc4cbfc480eb067 GIT binary patch literal 50 zcmbmv^%jMCzfVjzIZaa)Z1y1Pq6fJCI+7# G!5{#;84|_- literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/11/9f6cd3535de0e2a15654947a7b1a5affbf1406 b/tests/resources/nasty/.gitted/objects/11/9f6cd3535de0e2a15654947a7b1a5affbf1406 new file mode 100644 index 0000000000000000000000000000000000000000..fb03b26b09f8bc42651d6ce3c201985d1e5a4873 GIT binary patch literal 50 zcmbmv^%jMCzfVjzIZaa)Z1y1Pq6fJCI+A7 Gg24dHYZGDs literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/15/f7d9f9514eeb65b9588c49b10b1da145a729a2 b/tests/resources/nasty/.gitted/objects/15/f7d9f9514eeb65b9588c49b10b1da145a729a2 new file mode 100644 index 0000000000000000000000000000000000000000..a7f3683e4180c40009ce319435a8c8f745c83f77 GIT binary patch literal 137 zcmV;40CxX)0iBI84#F@DMVWI7??@W@ahX10} zx+=I}ciNbN(UToBy6ha?L&B68hnz9^Wf^cnvOc?+4ZXBTgG+i-OK>YvH+jI3yZxT(~L`iz3rGTdsq4Y2Xm!OKTs*cleByR7#2VCP>4aA literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/16/35c47d80914f0abfa43dd4234a948db5bdb107 b/tests/resources/nasty/.gitted/objects/16/35c47d80914f0abfa43dd4234a948db5bdb107 new file mode 100644 index 0000000000000000000000000000000000000000..f82b82be76ad8404e82dc9bf529479a0b16d83ff GIT binary patch literal 137 zcmV;40CxX)0iBIK4gw(%g{?Wo^d<(DpM}I28(S+cz`~H-0%rqbjJFrB;A`H?S9~u~ zts8?1^1uxdyqt`&9%n=olCiF2KPqb*6o#PPQk>F@7V*U^HMr$>Y#AOa)lN@19VK6ZO0)wE{~u20cAVbOF)t~b<7xQC)dh7d3_?o0 F@c^M25!Cjm?>F1-XnT6^wL!-T+*9rg4-&k$penL+aI0F q7OOO2Jwz!d7)Cp}*@p%8*k`)zzncFqm@75?K%odv()9}jV?X4hi9g5y literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/44/2894787eddb1e84a952f17a027590e2c6c02cd b/tests/resources/nasty/.gitted/objects/44/2894787eddb1e84a952f17a027590e2c6c02cd new file mode 100644 index 0000000000000000000000000000000000000000..c81b0e67a804d4238448248721e1fedf7ddeed73 GIT binary patch literal 137 zcmV;40CxX)0iBI84#F@DMVWI7??|W(O-Lk!SXi03K%6>i2jeO+LfoEk1>W@ahX1lK zuL@%vc3O#mFjy=udi1`;fvhVbjF}=vo6$K;IF-3LlU}+=h1>j2b%w_x+~f&o>h`y; ryyhxRiXO7?1x9|Le*AIv>9{Q##7ThiqVj{!isi~&HO literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/46/fe10fa23259b089ab050788b06df979cd7d054 b/tests/resources/nasty/.gitted/objects/46/fe10fa23259b089ab050788b06df979cd7d054 new file mode 100644 index 0000000000000000000000000000000000000000..6d1f52df992216f0467f813474b75c38bacbee96 GIT binary patch literal 137 zcmV;40CxX)0iBI84gw(%MXfo-^d<&a*ku!AY;3K(01Obez}dhU)fIdvx?5TkW7#nXQiz&UJe0`6#m798r!$}9KW3YUl4cS`2*D>?4HXP=OaI?ZS+B= literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/6b/7d8a5a48a3c753b75a8fe5196f9c8704ac64ad b/tests/resources/nasty/.gitted/objects/6b/7d8a5a48a3c753b75a8fe5196f9c8704ac64ad new file mode 100644 index 0000000000000000000000000000000000000000..121277fdfa40ef6654c518f0248e45a62a46d468 GIT binary patch literal 50 zcmb2o)b_2 literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/71/2ceb8eb3e57072447715bc4057c57aa50f629a b/tests/resources/nasty/.gitted/objects/71/2ceb8eb3e57072447715bc4057c57aa50f629a new file mode 100644 index 0000000000000000000000000000000000000000..9ed35d78a66fde762c8053efd176070c80ff55be GIT binary patch literal 138 zcmV;50CoR(0iBI84gw(%MXfo-^d<(_*@aDvv9Y!C0_*_UEpRq4#&~=23cl*+75@>J zvI&@3AEc%PcEQCtjGjhBvsml=G!7;Ym@_%&wWb(6Wffksa)n!bhZ^Cra%ud8bL#fD su4SXjjkBDH%LR-baGac<;ix;R%ifjr|AV@RCLbt)U=Ol>0p*!Lx}!Hl2mk;8 literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/7a/0538bc4e20aecb36ef221f2077eb30ebe0bcb2 b/tests/resources/nasty/.gitted/objects/7a/0538bc4e20aecb36ef221f2077eb30ebe0bcb2 new file mode 100644 index 0000000000000000000000000000000000000000..0c3ea2694d815d6dfc8e8624c50513508fa1003d GIT binary patch literal 136 zcmV;30C)d*0iBI84gw(%MXfo-^d<%tSRpaS#@5OU%)*e}0>{7@h#n~E7j=ZH3ibG?(!k4Vv;F{mqGTc|LogZ+bVSnpV qx74_EkwfBo1~Xab=VOSl*8?@==nDP+pl;aZ0~HfIQPwZg9VK6ZO0tG$yM-9E6Aq-UyOYW}t$hx%Y=;W=sB_UC!sf}|1 HJ}nT^s!tQb literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/88/6c0f5f71057d846f71f05a05fdffad332bc070 b/tests/resources/nasty/.gitted/objects/88/6c0f5f71057d846f71f05a05fdffad332bc070 new file mode 100644 index 0000000000000000000000000000000000000000..432eea2832e9673db3690e6717f2fefd357436e0 GIT binary patch literal 50 zcmbTux5O&BX?J2jPAtv5eDP#-skhT2pJ3_dObkA^ G1w#Sdi4+(B literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/96/156716851c0afb4702b0d2c4ac8c496a730e29 b/tests/resources/nasty/.gitted/objects/96/156716851c0afb4702b0d2c4ac8c496a730e29 new file mode 100644 index 0000000000000000000000000000000000000000..57419bc77d78b535c6c4714f6812706ae31e68ad GIT binary patch literal 137 zcmV;40CxX)0iBII4#F@H1*vn2?U7LY*$_(z(a=(HfwjF>QkX@~BE;=6S755ADc&Th zbyYB7+-YM5o2|=eFpQjRL08yEjL|VBnk+kH&t*m$=%tG^xaK#t4EIIqCJ#7qx4(6% rYihDd(L<7Y24kJa={N`2kDZ~*-c|npVYtzzAE=bzNxFOiT;xD+RYXG% literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/9a/b85e507899c19dca57778c9b6e5f1ec799b911 b/tests/resources/nasty/.gitted/objects/9a/b85e507899c19dca57778c9b6e5f1ec799b911 new file mode 100644 index 0000000000000000000000000000000000000000..aa24a8fc775557b43d1e6191cf88eb2299a74ada GIT binary patch literal 135 zcmV;20C@j+0iBIK3IZ_@My+#-=>^#}{~%&xYvl!ICkeYn97#sR+Z(Uot3F=wJ>pt7 zfjLe)X*q*id3~CvLe{rP_*Yx&1!M#!&^ng>I_O~u= qi=74)K16C~P#PCwkE?^doQNrBm-GJ*;^qf`p!o<-&-DxF4?m=Olsw)5 literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/a5/76a98d3279989226992610372035b76a01a3e9 b/tests/resources/nasty/.gitted/objects/a5/76a98d3279989226992610372035b76a01a3e9 new file mode 100644 index 0000000000000000000000000000000000000000..75fa458e758747804440cef38d59518a5fa7853c GIT binary patch literal 136 zcmV;30C)d*0iBI84#F@H1gZUs>yc0{Hi<1EL_^C literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/b1/1df9aee97a65817e8904a74f5e6a1c62c7a275 b/tests/resources/nasty/.gitted/objects/b1/1df9aee97a65817e8904a74f5e6a1c62c7a275 new file mode 100644 index 0000000000000000000000000000000000000000..b2e0eda1a44a425399be1040d97a2512a840234f GIT binary patch literal 50 zcmV-20L}k+0V^p=O;s>9VK6ZO0)_Xx_v@u+mM~O3EV;YlBkR(pqm#GlmV`u^rZ&z6 I09FMNtPGzO{Qv*} literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/bb/29ec85546d29b0bcc314242660d7772b0a3803 b/tests/resources/nasty/.gitted/objects/bb/29ec85546d29b0bcc314242660d7772b0a3803 new file mode 100644 index 0000000000000000000000000000000000000000..00ab02c217ff63114b4cde9edddc3d0977df798a GIT binary patch literal 50 zcmbmv^%jMCzfVjzIZaa)Z1y1Pq6fJCI+7( G!9W1IsuHRI literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/c2/a2ddd339574e5cbfd9228be840eb1bf496de4e b/tests/resources/nasty/.gitted/objects/c2/a2ddd339574e5cbfd9228be840eb1bf496de4e new file mode 100644 index 0000000000000000000000000000000000000000..939cf5576fea46a5ec60504a8b96ce4a23068110 GIT binary patch literal 137 zcmV;40CxX)0iBII4gxU@1*vli_edz-&4)+`(a=(H0dGFqErL;Egt$H73QYAh#hb{r zZUPwePFl`@7>bK&wzExH=ZVldYl89Q7bHuiECr2rie9pEgKK(IOK@Mgc7DJq5Bpn} ry2ZwwiyR`?GvG8E>-;gmUJuleqbvFU2X&(^AE*@JNwR(c0CPXvT;M*; literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/c3/a70f8a376f17adccfb52b48e2831bfef2a2172 b/tests/resources/nasty/.gitted/objects/c3/a70f8a376f17adccfb52b48e2831bfef2a2172 new file mode 100644 index 0000000000000000000000000000000000000000..b43d3f165dfb0f6d7d613a9feeef603d512807ee GIT binary patch literal 136 zcmV;30C)d*0iDe~3IZ_@1z_tv#q@$?CvG+aA~v>GULe0@i#U>uh_^Rh!B;)L;=RDN zZUQr@owSrdd5g-#xT4cud(xQwVlWd%q?u?4SxstkFDcyM8sDx(xEHRSA8<-Tf9+DY q(71D!L*ROb8TD~7t0ma0k!j%gmGu9@baP!kP+fqhlkx?f0zWaBmO(%O literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/c8/f98a1762ec016c30f0d73512df399dedefc3fd b/tests/resources/nasty/.gitted/objects/c8/f98a1762ec016c30f0d73512df399dedefc3fd new file mode 100644 index 0000000000000000000000000000000000000000..85ddc7f9be833e62d5c6e92bdee4171a6d45fe8b GIT binary patch literal 136 zcmV;30C)d*0iBIK3IZ_@My+#-=>^%u%^!%^*jjmkF_|S>#95LN@%F|m_^OXrd@pgW ztALKvP8tzBw9ZRJqUh1tpgn~+&FJUFl0%)GNgj>23SUyV!8O0JWw;lvou6=`VSnpV q*VMT4B8SBF4B8xL>wJi?p9X5k(G~jtLEW&+2P!7mP|6n>D?joaGDVmG literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/ce/22b3cd9a01efafc370879c1938e0c32fb6f195 b/tests/resources/nasty/.gitted/objects/ce/22b3cd9a01efafc370879c1938e0c32fb6f195 new file mode 100644 index 0000000000000000000000000000000000000000..eb5acc34b96cc77c7a96274b64c053cf6dea98c6 GIT binary patch literal 136 zcmV;30C)d*0iBII3PLduMXlc|rWYjt4~W>7)#ViTAoad7 z$S8I^YK5FttQf2{uBezrM%2Tux5O&BX?J2jPAtv5eDP#-skhT2pJ3_dObkB7 Gf`I_k4HIkt literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/objects/eb/82bf596b66f90e25f881ce9b92cb55bab4fdf5 b/tests/resources/nasty/.gitted/objects/eb/82bf596b66f90e25f881ce9b92cb55bab4fdf5 new file mode 100644 index 0000000000000000000000000000000000000000..b886096ce8c30aae71f0eebf9d3f4c851fbea785 GIT binary patch literal 50 zcmbTux5O&BX?J2jPAtv5eDP#-skhT2pJ3_dObk8| Gf&l=+yAsd< literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_1 b/tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_1 new file mode 100644 index 0000000000000000000000000000000000000000..dc48bd6fcd3c26f1768d142b6e5b9c4b7f75cadd GIT binary patch literal 41 ucmV~$NdW*L2n4{tX;$Qn10ww+IKw(`W0DR@6g;vZF!zG?gvD)Z2(J(0%L=6c literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_10 b/tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_10 new file mode 100644 index 0000000000000000000000000000000000000000..b3a972629e36ff250cd5a8d2c8a2d420a49348c0 GIT binary patch literal 41 tcmV~$u>k-e2m`R)YwUof=Rn2(PcTt+#>8eyhOR3}rqyTSVN9t7tPktQ3i$v4 literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_11 b/tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_11 new file mode 100644 index 0000000000000000000000000000000000000000..edf27988a1526d088de167b6debfb2a6a3c13500 GIT binary patch literal 41 vcmV~$!4Uu;2m`Rc(^!s*9S21BAHf8C(Rw2M-dZei@8c@WLMNOVrnK<^>e&j` literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_12 b/tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_12 new file mode 100644 index 0000000000000000000000000000000000000000..c4e682e103a30973ce5f78b31f41cca235ffce76 GIT binary patch literal 41 ucmV~$K>+|D2m`>sX=JFQ4xsBF!Mpb4RHXCBn73QCQ7az8t@MP15#s~;sSASu literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_13 b/tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_13 new file mode 100644 index 0000000000000000000000000000000000000000..76a155c208f17d62ba8cb04aaf31e28c9531e7ad GIT binary patch literal 41 ucmV~$K>+|D2m`>sX%tW)jpmBWqz6-Ik3d4T>b#x1L7};miYynfzzE1c_F@g>g9@4e literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_15 b/tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_15 new file mode 100644 index 0000000000000000000000000000000000000000..3fdeecea69b7fd8a140b1627f0c8477b660bc345 GIT binary patch literal 41 ucmV~$Nf7`r2n4Wy)o>21917IGgeH2$);pYc(b0){Dn$cFSXSu}v-J7z7z+OY literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_16 b/tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_16 new file mode 100644 index 0000000000000000000000000000000000000000..2739555f7a914b3dd47eb80ae39b80cd4d0b923f GIT binary patch literal 41 vcmV~$#}NQ92m`QxrlDLYqVra?T6gKGZ~x=XOZ8DTtBuhmJKj*QmCXrg@6m0Ta#LJDU9 literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_4 b/tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_4 new file mode 100644 index 0000000000000000000000000000000000000000..754b55edd5d935cbc124e1faade824d6a8475a59 GIT binary patch literal 41 tcmV~$!4Uu;2m`Rc)6}BkI+UyXk6@C89ap_flv{E9$oYs&D_|LFaeVU~3!DG| literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_5 b/tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_5 new file mode 100644 index 0000000000000000000000000000000000000000..161ebc43b517cb055e02c42e6fa4ad04efcf2ba2 GIT binary patch literal 41 ucmV~$!4Uu;2m`Rc(|}Zgaljt?k6@zL&Vw@~6(iMCFcRBPiC~{*tN8ll4+`%9 literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_6 b/tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_6 new file mode 100644 index 0000000000000000000000000000000000000000..f8a5fa3f7eabf1f1213d244914092feb4701bb4f GIT binary patch literal 41 tcmV~$NdW*L2n4{tX^@*Z;L^W>GqtS24p+;XCRQPIzMZkSjE0z%;C$gl3d{fi literal 0 HcmV?d00001 diff --git a/tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_7 b/tests/resources/nasty/.gitted/refs/heads/dotgit_hfs_ignorable_7 new file mode 100644 index 0000000000000000000000000000000000000000..ad5ad1d70b89635af31e352fa5e57f32e275a515 GIT binary patch literal 41 vcmV~$!4Uu;2m`Rc(*QjM#=#2vk6^+!yWq Date: Tue, 16 Dec 2014 13:03:02 +0100 Subject: [PATCH 09/11] path: Use UTF8 iteration for HFS chars --- src/path.c | 124 +++++++++++++++++++---------------------------------- src/util.c | 76 ++++++++++++++++++++++++++++++++ src/util.h | 11 +++++ 3 files changed, 131 insertions(+), 80 deletions(-) diff --git a/src/path.c b/src/path.c index 724d9ede2..b9c9729c1 100644 --- a/src/path.c +++ b/src/path.c @@ -1282,93 +1282,57 @@ GIT_INLINE(bool) verify_dospath( component[last] != ':'); } -GIT_INLINE(bool) verify_dotgit_hfs(const char *component, size_t len) +static int32_t next_hfs_char(const char **in, size_t *len) { - const unsigned char *c; - int git = 0, ign = 0; - unsigned char one, two; + while (*len) { + int32_t codepoint; + int cp_len = git__utf8_iterate((const uint8_t *)(*in), (int)(*len), &codepoint); + if (cp_len < 0) + return -1; - while (len) { - switch (*(c = (const unsigned char *)component++)) { - case '.': - if (ign || git++ != 0) - return true; - break; - case 'g': - case 'G': - if (ign || git++ != 1) - return true; - break; - case 'i': - case 'I': - if (ign || git++ != 2) - return true; - break; - case 't': - case 'T': - if (ign || git++ != 3) - return true; - break; + (*in) += cp_len; + (*len) -= cp_len; - case 0xe2: - case 0xef: - if (ign++ != 0) - return true; - one = *c; - break; - - case 0x80: - case 0x81: - if (ign++ != 1 || one != 0xe2) - return true; - two = *c; - break; - - case 0xbb: - if (ign++ != 1 || one != 0xef) - return true; - two = *c; - break; - - case 0x8c: - case 0x8d: - case 0x8e: - case 0x8f: - if (ign != 2 || two != 0x80) - return true; - ign = 0; - break; - - case 0xaa: - case 0xab: - case 0xac: - case 0xad: - case 0xae: - if (ign != 2 || (two != 0x80 && two != 0x81)) - return true; - ign = 0; - break; - - case 0xaf: - if (ign != 2 || two != 0x81) - return true; - ign = 0; - break; - - case 0xbf: - if (ign != 2 || two != 0xbb) - return true; - ign = 0; - break; - - default: - return true; + /* these code points are ignored completely */ + switch (codepoint) { + case 0x200c: /* ZERO WIDTH NON-JOINER */ + case 0x200d: /* ZERO WIDTH JOINER */ + case 0x200e: /* LEFT-TO-RIGHT MARK */ + case 0x200f: /* RIGHT-TO-LEFT MARK */ + case 0x202a: /* LEFT-TO-RIGHT EMBEDDING */ + case 0x202b: /* RIGHT-TO-LEFT EMBEDDING */ + case 0x202c: /* POP DIRECTIONAL FORMATTING */ + case 0x202d: /* LEFT-TO-RIGHT OVERRIDE */ + case 0x202e: /* RIGHT-TO-LEFT OVERRIDE */ + case 0x206a: /* INHIBIT SYMMETRIC SWAPPING */ + case 0x206b: /* ACTIVATE SYMMETRIC SWAPPING */ + case 0x206c: /* INHIBIT ARABIC FORM SHAPING */ + case 0x206d: /* ACTIVATE ARABIC FORM SHAPING */ + case 0x206e: /* NATIONAL DIGIT SHAPES */ + case 0x206f: /* NOMINAL DIGIT SHAPES */ + case 0xfeff: /* ZERO WIDTH NO-BREAK SPACE */ + continue; } - len--; + /* fold into lowercase -- this will only fold characters in + * the ASCII range, which is perfectly fine, because the + * git folder name can only be composed of ascii characters + */ + return tolower(codepoint); } + return 0; /* NULL byte -- end of string */ +} - return (ign || git != 4); +static bool verify_dotgit_hfs(const char *path, size_t len) +{ + if (next_hfs_char(&path, &len) != '.' || + next_hfs_char(&path, &len) != 'g' || + next_hfs_char(&path, &len) != 'i' || + next_hfs_char(&path, &len) != 't' || + next_hfs_char(&path, &len) != 0) + return true; + + return false; } GIT_INLINE(bool) verify_char(unsigned char c, unsigned int flags) diff --git a/src/util.c b/src/util.c index 6b0efbea5..7ee3e2ff9 100644 --- a/src/util.c +++ b/src/util.c @@ -664,3 +664,79 @@ void git__insertsort_r( if (freeswap) git__free(swapel); } + +static const int8_t utf8proc_utf8class[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +int git__utf8_charlen(const uint8_t *str, int str_len) +{ + int length, i; + + length = utf8proc_utf8class[str[0]]; + if (!length) + return -1; + + if (str_len >= 0 && length > str_len) + return -str_len; + + for (i = 1; i < length; i++) { + if ((str[i] & 0xC0) != 0x80) + return -i; + } + + return length; +} + +int git__utf8_iterate(const uint8_t *str, int str_len, int32_t *dst) +{ + int length; + int32_t uc = -1; + + *dst = -1; + length = git__utf8_charlen(str, str_len); + if (length < 0) + return -1; + + switch (length) { + case 1: + uc = str[0]; + break; + case 2: + uc = ((str[0] & 0x1F) << 6) + (str[1] & 0x3F); + if (uc < 0x80) uc = -1; + break; + case 3: + uc = ((str[0] & 0x0F) << 12) + ((str[1] & 0x3F) << 6) + + (str[2] & 0x3F); + if (uc < 0x800 || (uc >= 0xD800 && uc < 0xE000) || + (uc >= 0xFDD0 && uc < 0xFDF0)) uc = -1; + break; + case 4: + uc = ((str[0] & 0x07) << 18) + ((str[1] & 0x3F) << 12) + + ((str[2] & 0x3F) << 6) + (str[3] & 0x3F); + if (uc < 0x10000 || uc >= 0x110000) uc = -1; + break; + } + + if (uc < 0 || ((uc & 0xFFFF) >= 0xFFFE)) + return -1; + + *dst = uc; + return length; +} diff --git a/src/util.h b/src/util.h index 17cc08987..7cfc0d644 100644 --- a/src/util.h +++ b/src/util.h @@ -367,6 +367,17 @@ extern int git__date_rfc2822_fmt(char *out, size_t len, const git_time *date); */ extern size_t git__unescape(char *str); +/* + * Iterate through an UTF-8 string, yielding one + * codepoint at a time. + * + * @param str current position in the string + * @param str_len size left in the string; -1 if the string is NULL-terminated + * @param dst pointer where to store the current codepoint + * @return length in bytes of the read codepoint; -1 if the codepoint was invalid + */ +extern int git__utf8_iterate(const uint8_t *str, int str_len, int32_t *dst); + /* * Safely zero-out memory, making sure that the compiler * doesn't optimize away the operation. From ec74b40ceef3dc3892c7d84bb4f5d99bab504ba4 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 16 Dec 2014 18:53:55 -0600 Subject: [PATCH 10/11] Introduce core.protectHFS and core.protectNTFS Validate HFS ignored char ".git" paths when `core.protectHFS` is specified. Validate NTFS invalid ".git" paths when `core.protectNTFS` is specified. --- src/config_cache.c | 2 + src/path.c | 113 ++++++++++++++++++++++++++++++----------- src/path.h | 20 ++++---- src/repository.h | 6 +++ tests/checkout/nasty.c | 32 ++++++++++++ tests/path/core.c | 22 ++++++-- 6 files changed, 151 insertions(+), 44 deletions(-) diff --git a/src/config_cache.c b/src/config_cache.c index 45c39ce17..d397a4bab 100644 --- a/src/config_cache.c +++ b/src/config_cache.c @@ -76,6 +76,8 @@ static struct map_data _cvar_maps[] = { {"core.precomposeunicode", NULL, 0, GIT_PRECOMPOSE_DEFAULT }, {"core.safecrlf", _cvar_map_safecrlf, ARRAY_SIZE(_cvar_map_safecrlf), GIT_SAFE_CRLF_DEFAULT}, {"core.logallrefupdates", NULL, 0, GIT_LOGALLREFUPDATES_DEFAULT }, + {"core.protecthfs", NULL, 0, GIT_PROTECTHFS_DEFAULT }, + {"core.protectntfs", NULL, 0, GIT_PROTECTNTFS_DEFAULT }, }; int git_config__cvar(int *out, git_config *config, git_cvar_cached cvar) diff --git a/src/path.c b/src/path.c index b9c9729c1..768a7e188 100644 --- a/src/path.c +++ b/src/path.c @@ -1240,26 +1240,6 @@ int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path) return git_buf_sets(local_path_out, url_or_path); } -GIT_INLINE(bool) verify_shortname( - git_repository *repo, - const char *component, - size_t len) -{ - const char *shortname_repo; - - if (len == git_repository__8dot3_default_len && - strncasecmp(git_repository__8dot3_default, component, len) == 0) - return false; - - if (repo && - (shortname_repo = git_repository__8dot3_name(repo)) && - shortname_repo != git_repository__8dot3_default && - git__prefixncmp_icase(component, len, shortname_repo) == 0) - return false; - - return true; -} - /* Reject paths like AUX or COM1, or those versions that end in a dot or * colon. ("AUX." or "AUX:") */ @@ -1335,11 +1315,48 @@ static bool verify_dotgit_hfs(const char *path, size_t len) return false; } +GIT_INLINE(bool) verify_dotgit_ntfs(git_repository *repo, const char *path, size_t len) +{ + const char *shortname = NULL; + size_t i, start, shortname_len = 0; + + /* See if the repo has a custom shortname (not "GIT~1") */ + if (repo && + (shortname = git_repository__8dot3_name(repo)) && + shortname != git_repository__8dot3_default) + shortname_len = strlen(shortname); + + if (len >= 4 && strncasecmp(path, ".git", 4) == 0) + start = 4; + else if (len >= git_repository__8dot3_default_len && + strncasecmp(path, git_repository__8dot3_default, git_repository__8dot3_default_len) == 0) + start = git_repository__8dot3_default_len; + else if (shortname_len && len >= shortname_len && + strncasecmp(path, shortname, shortname_len) == 0) + start = shortname_len; + else + return true; + + /* Reject paths beginning with ".git\" */ + if (path[start] == '\\') + return false; + + for (i = start; i < len; i++) { + if (path[i] != ' ' && path[i] != '.') + return true; + } + + return false; +} + GIT_INLINE(bool) verify_char(unsigned char c, unsigned int flags) { if ((flags & GIT_PATH_REJECT_BACKSLASH) && c == '\\') return false; + if ((flags & GIT_PATH_REJECT_SLASH) && c == '/') + return false; + if (flags & GIT_PATH_REJECT_NT_CHARS) { if (c < 32) return false; @@ -1385,13 +1402,6 @@ static bool verify_component( len == 2 && component[0] == '.' && component[1] == '.') return false; - if ((flags & GIT_PATH_REJECT_DOT_GIT) && len == 4 && - component[0] == '.' && - (component[1] == 'g' || component[1] == 'G') && - (component[2] == 'i' || component[2] == 'I') && - (component[3] == 't' || component[3] == 'T')) - return false; - if ((flags & GIT_PATH_REJECT_TRAILING_DOT) && component[len-1] == '.') return false; @@ -1401,10 +1411,6 @@ static bool verify_component( if ((flags & GIT_PATH_REJECT_TRAILING_COLON) && component[len-1] == ':') return false; - if ((flags & GIT_PATH_REJECT_DOS_GIT_SHORTNAME) && - !verify_shortname(repo, component, len)) - return false; - if (flags & GIT_PATH_REJECT_DOS_PATHS) { if (!verify_dospath(component, len, "CON", false) || !verify_dospath(component, len, "PRN", false) || @@ -1419,9 +1425,50 @@ static bool verify_component( !verify_dotgit_hfs(component, len)) return false; + if (flags & GIT_PATH_REJECT_DOT_GIT_NTFS && + !verify_dotgit_ntfs(repo, component, len)) + return false; + + if ((flags & GIT_PATH_REJECT_DOT_GIT_HFS) == 0 && + (flags & GIT_PATH_REJECT_DOT_GIT_NTFS) == 0 && + (flags & GIT_PATH_REJECT_DOT_GIT) && + len == 4 && + component[0] == '.' && + (component[1] == 'g' || component[1] == 'G') && + (component[2] == 'i' || component[2] == 'I') && + (component[3] == 't' || component[3] == 'T')) + return false; + return true; } +GIT_INLINE(unsigned int) dotgit_flags( + git_repository *repo, + unsigned int flags) +{ + int protectHFS = 0, protectNTFS = 0; + +#ifdef __APPLE__ + protectHFS = 1; +#endif + +#ifdef GIT_WIN32 + protectNTFS = 1; +#endif + + if (repo && !protectHFS) + git_repository__cvar(&protectHFS, repo, GIT_CVAR_PROTECTHFS); + if (protectHFS) + flags |= GIT_PATH_REJECT_DOT_GIT_HFS; + + if (repo && !protectNTFS) + git_repository__cvar(&protectNTFS, repo, GIT_CVAR_PROTECTNTFS); + if (protectNTFS) + flags |= GIT_PATH_REJECT_DOT_GIT_NTFS; + + return flags; +} + bool git_path_isvalid( git_repository *repo, const char *path, @@ -1429,6 +1476,10 @@ bool git_path_isvalid( { const char *start, *c; + /* Upgrade the ".git" checks based on platform */ + if ((flags & GIT_PATH_REJECT_DOT_GIT)) + flags = dotgit_flags(repo, flags); + for (start = c = path; *c; c++) { if (!verify_char(*c, flags)) return false; diff --git a/src/path.h b/src/path.h index 4e25d9378..b753140b2 100644 --- a/src/path.h +++ b/src/path.h @@ -465,15 +465,20 @@ extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or /* Flags to determine path validity in `git_path_isvalid` */ #define GIT_PATH_REJECT_TRAVERSAL (1 << 0) #define GIT_PATH_REJECT_DOT_GIT (1 << 1) -#define GIT_PATH_REJECT_BACKSLASH (1 << 2) -#define GIT_PATH_REJECT_TRAILING_DOT (1 << 3) -#define GIT_PATH_REJECT_TRAILING_SPACE (1 << 4) -#define GIT_PATH_REJECT_TRAILING_COLON (1 << 5) -#define GIT_PATH_REJECT_DOS_GIT_SHORTNAME (1 << 6) +#define GIT_PATH_REJECT_SLASH (1 << 2) +#define GIT_PATH_REJECT_BACKSLASH (1 << 3) +#define GIT_PATH_REJECT_TRAILING_DOT (1 << 4) +#define GIT_PATH_REJECT_TRAILING_SPACE (1 << 5) +#define GIT_PATH_REJECT_TRAILING_COLON (1 << 6) #define GIT_PATH_REJECT_DOS_PATHS (1 << 7) #define GIT_PATH_REJECT_NT_CHARS (1 << 8) #define GIT_PATH_REJECT_DOT_GIT_HFS (1 << 9) +#define GIT_PATH_REJECT_DOT_GIT_NTFS (1 << 10) +/* Default path safety for writing files to disk: since we use the + * Win32 "File Namespace" APIs ("\\?\") we need to protect from + * paths that the normal Win32 APIs would not write. + */ #ifdef GIT_WIN32 # define GIT_PATH_REJECT_DEFAULTS \ GIT_PATH_REJECT_TRAVERSAL | \ @@ -481,13 +486,8 @@ extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or GIT_PATH_REJECT_TRAILING_DOT | \ GIT_PATH_REJECT_TRAILING_SPACE | \ GIT_PATH_REJECT_TRAILING_COLON | \ - GIT_PATH_REJECT_DOS_GIT_SHORTNAME | \ GIT_PATH_REJECT_DOS_PATHS | \ GIT_PATH_REJECT_NT_CHARS -#elif __APPLE__ -# define GIT_PATH_REJECT_DEFAULTS \ - GIT_PATH_REJECT_TRAVERSAL | \ - GIT_PATH_REJECT_DOT_GIT_HFS #else # define GIT_PATH_REJECT_DEFAULTS GIT_PATH_REJECT_TRAVERSAL #endif diff --git a/src/repository.h b/src/repository.h index d9b950aac..6da8c289b 100644 --- a/src/repository.h +++ b/src/repository.h @@ -40,6 +40,8 @@ typedef enum { GIT_CVAR_PRECOMPOSE, /* core.precomposeunicode */ GIT_CVAR_SAFE_CRLF, /* core.safecrlf */ GIT_CVAR_LOGALLREFUPDATES, /* core.logallrefupdates */ + GIT_CVAR_PROTECTHFS, /* core.protectHFS */ + GIT_CVAR_PROTECTNTFS, /* core.protectNTFS */ GIT_CVAR_CACHE_MAX } git_cvar_cached; @@ -96,6 +98,10 @@ typedef enum { /* core.logallrefupdates */ GIT_LOGALLREFUPDATES_UNSET = 2, GIT_LOGALLREFUPDATES_DEFAULT = GIT_LOGALLREFUPDATES_UNSET, + /* core.protectHFS */ + GIT_PROTECTHFS_DEFAULT = GIT_CVAR_FALSE, + /* core.protectNTFS */ + GIT_PROTECTNTFS_DEFAULT = GIT_CVAR_FALSE, } git_cvar_value; /* internal repository init flags */ diff --git a/tests/checkout/nasty.c b/tests/checkout/nasty.c index a667dcd8f..c07d9382a 100644 --- a/tests/checkout/nasty.c +++ b/tests/checkout/nasty.c @@ -291,3 +291,35 @@ void test_checkout_nasty__dot_git_hfs_ignorable(void) test_checkout_fails("refs/heads/dotgit_hfs_ignorable_16", ".git/foobar"); #endif } + +void test_checkout_nasty__honors_core_protecthfs(void) +{ + cl_repo_set_bool(repo, "core.protectHFS", true); + + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_1", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_2", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_3", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_4", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_5", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_6", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_7", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_8", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_9", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_10", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_11", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_12", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_13", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_14", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_15", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_16", ".git/foobar"); +} + +void test_checkout_nasty__honors_core_protectntfs(void) +{ + cl_repo_set_bool(repo, "core.protectNTFS", true); + + test_checkout_fails("refs/heads/dotgit_backslash_path", ".git/foobar"); + test_checkout_fails("refs/heads/dotcapitalgit_backslash_path", ".GIT/foobar"); + test_checkout_fails("refs/heads/dot_git_dot", ".git/foobar"); + test_checkout_fails("refs/heads/git_tilde1", ".git/foobar"); +} diff --git a/tests/path/core.c b/tests/path/core.c index 528108bea..85fee820a 100644 --- a/tests/path/core.c +++ b/tests/path/core.c @@ -172,11 +172,27 @@ void test_path_core__isvalid_trailing_colon(void) cl_assert_equal_b(false, git_path_isvalid(NULL, "foo:/bar", GIT_PATH_REJECT_TRAILING_COLON)); } -void test_path_core__isvalid_dos_git_shortname(void) +void test_path_core__isvalid_dotgit_ntfs(void) { - cl_assert_equal_b(true, git_path_isvalid(NULL, "git~1", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, ".git", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, ".git ", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, ".git.", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, ".git.. .", 0)); - cl_assert_equal_b(false, git_path_isvalid(NULL, "git~1", GIT_PATH_REJECT_DOS_GIT_SHORTNAME)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "git~1", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "git~1 ", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "git~1.", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "git~1.. .", 0)); + + cl_assert_equal_b(false, git_path_isvalid(NULL, ".git", GIT_PATH_REJECT_DOT_GIT_NTFS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, ".git ", GIT_PATH_REJECT_DOT_GIT_NTFS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, ".git.", GIT_PATH_REJECT_DOT_GIT_NTFS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, ".git.. .", GIT_PATH_REJECT_DOT_GIT_NTFS)); + + cl_assert_equal_b(false, git_path_isvalid(NULL, "git~1", GIT_PATH_REJECT_DOT_GIT_NTFS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "git~1 ", GIT_PATH_REJECT_DOT_GIT_NTFS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "git~1.", GIT_PATH_REJECT_DOT_GIT_NTFS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "git~1.. .", GIT_PATH_REJECT_DOT_GIT_NTFS)); } void test_path_core__isvalid_dos_paths(void) From dce7b1a4e75551804a33591744d40d0582c57cfb Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 16 Dec 2014 19:24:04 -0600 Subject: [PATCH 11/11] treebuilder: take a repository for path validation Path validation may be influenced by `core.protectHFS` and `core.protectNTFS` configuration settings, thus treebuilders can take a repository to influence their configuration. --- CHANGELOG.md | 4 ++ include/git2/tree.h | 6 +-- src/notes.c | 4 +- src/tree.c | 30 ++++++------ src/tree.h | 1 + tests/index/tests.c | 21 ++++++++ tests/object/tree/attributes.c | 30 ++++++------ tests/object/tree/duplicateentries.c | 4 +- tests/object/tree/write.c | 72 +++++++++++++++++++++------- tests/repo/iterator.c | 4 +- 10 files changed, 123 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfb4f0430..5dfc27edf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -136,3 +136,7 @@ v0.21 + 1 * git_libgit2_init() and git_libgit2_shutdown() now return the number of initializations of the library, so consumers may schedule work on the first initialization. + +* git_treebuilder_create now takes a repository so that it can query + repository configuration. Subsequently, git_treebuilder_write no + longer takes a repository. diff --git a/include/git2/tree.h b/include/git2/tree.h index 42b68193e..0f7616210 100644 --- a/include/git2/tree.h +++ b/include/git2/tree.h @@ -247,11 +247,12 @@ GIT_EXTERN(int) git_tree_entry_to_object( * entries and will have to be filled manually. * * @param out Pointer where to store the tree builder + * @param repo Repository in which to store the object * @param source Source tree to initialize the builder (optional) * @return 0 on success; error code otherwise */ GIT_EXTERN(int) git_treebuilder_create( - git_treebuilder **out, const git_tree *source); + git_treebuilder **out, git_repository *repo, const git_tree *source); /** * Clear all the entires in the builder @@ -368,12 +369,11 @@ GIT_EXTERN(void) git_treebuilder_filter( * identifying SHA1 hash will be stored in the `id` pointer. * * @param id Pointer to store the OID of the newly written tree - * @param repo Repository in which to store the object * @param bld Tree builder to write * @return 0 or an error code */ GIT_EXTERN(int) git_treebuilder_write( - git_oid *id, git_repository *repo, git_treebuilder *bld); + git_oid *id, git_treebuilder *bld); /** Callback for the tree traversal method */ diff --git a/src/notes.c b/src/notes.c index 8fdf388ab..a0bc0d355 100644 --- a/src/notes.c +++ b/src/notes.c @@ -107,7 +107,7 @@ static int tree_write( const git_tree_entry *entry; git_oid tree_oid; - if ((error = git_treebuilder_create(&tb, source_tree)) < 0) + if ((error = git_treebuilder_create(&tb, repo, source_tree)) < 0) goto cleanup; if (object_oid) { @@ -119,7 +119,7 @@ static int tree_write( goto cleanup; } - if ((error = git_treebuilder_write(&tree_oid, repo, tb)) < 0) + if ((error = git_treebuilder_write(&tree_oid, tb)) < 0) goto cleanup; error = git_tree_lookup(out, repo, &tree_oid); diff --git a/src/tree.c b/src/tree.c index 6246ff648..57cc95387 100644 --- a/src/tree.c +++ b/src/tree.c @@ -50,14 +50,11 @@ GIT_INLINE(git_filemode_t) normalize_filemode(git_filemode_t filemode) return GIT_FILEMODE_BLOB; } -static int valid_entry_name(const char *filename) +static int valid_entry_name(git_repository *repo, const char *filename) { return *filename != '\0' && - strchr(filename, '/') == NULL && - (*filename != '.' || - (strcmp(filename, ".") != 0 && - strcmp(filename, "..") != 0 && - strcasecmp(filename, DOT_GIT) != 0)); + git_path_isvalid(repo, filename, + GIT_PATH_REJECT_TRAVERSAL | GIT_PATH_REJECT_DOT_GIT | GIT_PATH_REJECT_SLASH); } static int entry_sort_cmp(const void *a, const void *b) @@ -455,7 +452,7 @@ static int append_entry( git_tree_entry *entry; int error = 0; - if (!valid_entry_name(filename)) + if (!valid_entry_name(bld->repo, filename)) return tree_error("Failed to insert entry. Invalid name for a tree entry", filename); entry = alloc_entry(filename); @@ -493,7 +490,7 @@ static int write_tree( return (int)find_next_dir(dirname, index, start); } - if ((error = git_treebuilder_create(&bld, NULL)) < 0 || bld == NULL) + if ((error = git_treebuilder_create(&bld, repo, NULL)) < 0 || bld == NULL) return -1; /* @@ -564,7 +561,7 @@ static int write_tree( } } - if (git_treebuilder_write(oid, repo, bld) < 0) + if (git_treebuilder_write(oid, bld) < 0) goto on_error; git_treebuilder_free(bld); @@ -627,16 +624,21 @@ int git_tree__write_index( return ret; } -int git_treebuilder_create(git_treebuilder **builder_p, const git_tree *source) +int git_treebuilder_create( + git_treebuilder **builder_p, + git_repository *repo, + const git_tree *source) { git_treebuilder *bld; size_t i; - assert(builder_p); + assert(builder_p && repo); bld = git__calloc(1, sizeof(git_treebuilder)); GITERR_CHECK_ALLOC(bld); + bld->repo = repo; + if (git_strmap_alloc(&bld->map) < 0) { git__free(bld); return -1; @@ -678,7 +680,7 @@ int git_treebuilder_insert( if (!valid_filemode(filemode)) return tree_error("Failed to insert entry. Invalid filemode for file", filename); - if (!valid_entry_name(filename)) + if (!valid_entry_name(bld->repo, filename)) return tree_error("Failed to insert entry. Invalid name for a tree entry", filename); pos = git_strmap_lookup_index(bld->map, filename); @@ -738,7 +740,7 @@ int git_treebuilder_remove(git_treebuilder *bld, const char *filename) return 0; } -int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *bld) +int git_treebuilder_write(git_oid *oid, git_treebuilder *bld) { int error = 0; size_t i, entrycount; @@ -777,7 +779,7 @@ int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *b git_vector_free(&entries); if (!error && - !(error = git_repository_odb__weakptr(&odb, repo))) + !(error = git_repository_odb__weakptr(&odb, bld->repo))) error = git_odb_write(oid, odb, tree.ptr, tree.size, GIT_OBJ_TREE); git_buf_free(&tree); diff --git a/src/tree.h b/src/tree.h index 5d27eb7c9..d01b6fd41 100644 --- a/src/tree.h +++ b/src/tree.h @@ -26,6 +26,7 @@ struct git_tree { }; struct git_treebuilder { + git_repository *repo; git_strmap *map; }; diff --git a/tests/index/tests.c b/tests/index/tests.c index 0464e7337..a6c4b895c 100644 --- a/tests/index/tests.c +++ b/tests/index/tests.c @@ -421,6 +421,27 @@ void test_index_tests__write_invalid_filename(void) cl_fixture_cleanup("invalid"); } +void test_index_tests__honors_protect_filesystems(void) +{ + git_repository *repo; + + p_mkdir("invalid", 0700); + + cl_git_pass(git_repository_init(&repo, "./invalid", 0)); + + cl_repo_set_bool(repo, "core.protectHFS", true); + cl_repo_set_bool(repo, "core.protectNTFS", true); + + write_invalid_filename(repo, ".git./hello"); + write_invalid_filename(repo, ".git\xe2\x80\xad/hello"); + write_invalid_filename(repo, "git~1/hello"); + write_invalid_filename(repo, ".git\xe2\x81\xaf/hello"); + + git_repository_free(repo); + + cl_fixture_cleanup("invalid"); +} + void test_index_tests__remove_entry(void) { git_repository *repo; diff --git a/tests/object/tree/attributes.c b/tests/object/tree/attributes.c index 85216cd1b..14f3f89f9 100644 --- a/tests/object/tree/attributes.c +++ b/tests/object/tree/attributes.c @@ -1,9 +1,21 @@ #include "clar_libgit2.h" #include "tree.h" +static git_repository *repo; + static const char *blob_oid = "3d0970ec547fc41ef8a5882dde99c6adce65b021"; static const char *tree_oid = "1b05fdaa881ee45b48cbaa5e9b037d667a47745e"; +void test_object_tree_attributes__initialize(void) +{ + repo = cl_git_sandbox_init("deprecated-mode.git"); +} + +void test_object_tree_attributes__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + void test_object_tree_attributes__ensure_correctness_of_attributes_on_insertion(void) { git_treebuilder *builder; @@ -11,7 +23,7 @@ void test_object_tree_attributes__ensure_correctness_of_attributes_on_insertion( cl_git_pass(git_oid_fromstr(&oid, blob_oid)); - cl_git_pass(git_treebuilder_create(&builder, NULL)); + cl_git_pass(git_treebuilder_create(&builder, repo, NULL)); cl_git_fail(git_treebuilder_insert(NULL, builder, "one.txt", &oid, (git_filemode_t)0777777)); cl_git_fail(git_treebuilder_insert(NULL, builder, "one.txt", &oid, (git_filemode_t)0100666)); @@ -22,7 +34,6 @@ void test_object_tree_attributes__ensure_correctness_of_attributes_on_insertion( void test_object_tree_attributes__group_writable_tree_entries_created_with_an_antique_git_version_can_still_be_accessed(void) { - git_repository *repo; git_oid tid; git_tree *tree; const git_tree_entry *entry; @@ -38,7 +49,6 @@ void test_object_tree_attributes__group_writable_tree_entries_created_with_an_an git_tree_entry_filemode(entry)); git_tree_free(tree); - git_repository_free(repo); } void test_object_tree_attributes__treebuilder_reject_invalid_filemode(void) @@ -48,7 +58,7 @@ void test_object_tree_attributes__treebuilder_reject_invalid_filemode(void) const git_tree_entry *entry; cl_git_pass(git_oid_fromstr(&bid, blob_oid)); - cl_git_pass(git_treebuilder_create(&builder, NULL)); + cl_git_pass(git_treebuilder_create(&builder, repo, NULL)); cl_git_fail(git_treebuilder_insert( &entry, @@ -62,25 +72,22 @@ void test_object_tree_attributes__treebuilder_reject_invalid_filemode(void) void test_object_tree_attributes__normalize_attributes_when_creating_a_tree_from_an_existing_one(void) { - git_repository *repo; git_treebuilder *builder; git_oid tid, tid2; git_tree *tree; const git_tree_entry *entry; - repo = cl_git_sandbox_init("deprecated-mode.git"); - cl_git_pass(git_oid_fromstr(&tid, tree_oid)); cl_git_pass(git_tree_lookup(&tree, repo, &tid)); - cl_git_pass(git_treebuilder_create(&builder, tree)); + cl_git_pass(git_treebuilder_create(&builder, repo, tree)); entry = git_treebuilder_get(builder, "old_mode.txt"); cl_assert_equal_i( GIT_FILEMODE_BLOB, git_tree_entry_filemode(entry)); - cl_git_pass(git_treebuilder_write(&tid2, repo, builder)); + cl_git_pass(git_treebuilder_write(&tid2, builder)); git_treebuilder_free(builder); git_tree_free(tree); @@ -91,18 +98,14 @@ void test_object_tree_attributes__normalize_attributes_when_creating_a_tree_from git_tree_entry_filemode(entry)); git_tree_free(tree); - cl_git_sandbox_cleanup(); } void test_object_tree_attributes__normalize_600(void) { git_oid id; git_tree *tree; - git_repository *repo; const git_tree_entry *entry; - repo = cl_git_sandbox_init("deprecated-mode.git"); - git_oid_fromstr(&id, "0810fb7818088ff5ac41ee49199b51473b1bd6c7"); cl_git_pass(git_tree_lookup(&tree, repo, &id)); @@ -111,5 +114,4 @@ void test_object_tree_attributes__normalize_600(void) cl_assert_equal_i(git_tree_entry_filemode_raw(entry), 0100600); git_tree_free(tree); - cl_git_sandbox_cleanup(); } diff --git a/tests/object/tree/duplicateentries.c b/tests/object/tree/duplicateentries.c index 1b752acbb..11314ec90 100644 --- a/tests/object/tree/duplicateentries.c +++ b/tests/object/tree/duplicateentries.c @@ -57,11 +57,11 @@ static void tree_creator(git_oid *out, void (*fn)(git_treebuilder *)) { git_treebuilder *builder; - cl_git_pass(git_treebuilder_create(&builder, NULL)); + cl_git_pass(git_treebuilder_create(&builder, _repo, NULL)); fn(builder); - cl_git_pass(git_treebuilder_write(out, _repo, builder)); + cl_git_pass(git_treebuilder_write(out, builder)); git_treebuilder_free(builder); } diff --git a/tests/object/tree/write.c b/tests/object/tree/write.c index ddb62e278..2947ac362 100644 --- a/tests/object/tree/write.c +++ b/tests/object/tree/write.c @@ -35,7 +35,7 @@ void test_object_tree_write__from_memory(void) * on REPOSITORY_FOLDER. */ cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); - cl_git_pass(git_treebuilder_create(&builder, tree)); + cl_git_pass(git_treebuilder_create(&builder, g_repo, tree)); cl_git_fail(git_treebuilder_insert(NULL, builder, "", &bid, GIT_FILEMODE_BLOB)); @@ -53,7 +53,7 @@ void test_object_tree_write__from_memory(void) cl_git_pass(git_treebuilder_insert( NULL, builder, "new.txt", &bid, GIT_FILEMODE_BLOB)); - cl_git_pass(git_treebuilder_write(&rid, g_repo, builder)); + cl_git_pass(git_treebuilder_write(&rid, builder)); cl_assert(git_oid_cmp(&rid, &id2) == 0); @@ -75,18 +75,18 @@ void test_object_tree_write__subtree(void) git_oid_fromstr(&bid, blob_oid); /* create subtree */ - cl_git_pass(git_treebuilder_create(&builder, NULL)); + cl_git_pass(git_treebuilder_create(&builder, g_repo, NULL)); cl_git_pass(git_treebuilder_insert( NULL, builder, "new.txt", &bid, GIT_FILEMODE_BLOB)); /* -V536 */ - cl_git_pass(git_treebuilder_write(&subtree_id, g_repo, builder)); + cl_git_pass(git_treebuilder_write(&subtree_id, builder)); git_treebuilder_free(builder); /* create parent tree */ cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); - cl_git_pass(git_treebuilder_create(&builder, tree)); + cl_git_pass(git_treebuilder_create(&builder, g_repo, tree)); cl_git_pass(git_treebuilder_insert( NULL, builder, "new", &subtree_id, GIT_FILEMODE_TREE)); /* -V536 */ - cl_git_pass(git_treebuilder_write(&id_hiearar, g_repo, builder)); + cl_git_pass(git_treebuilder_write(&id_hiearar, builder)); git_treebuilder_free(builder); git_tree_free(tree); @@ -135,14 +135,14 @@ void test_object_tree_write__sorted_subtrees(void) memset(&blank_oid, 0x0, sizeof(blank_oid)); - cl_git_pass(git_treebuilder_create(&builder, NULL)); + cl_git_pass(git_treebuilder_create(&builder, g_repo, NULL)); for (i = 0; i < ARRAY_SIZE(entries); ++i) { cl_git_pass(git_treebuilder_insert(NULL, builder, entries[i].filename, &blank_oid, entries[i].attr)); } - cl_git_pass(git_treebuilder_write(&tree_oid, g_repo, builder)); + cl_git_pass(git_treebuilder_write(&tree_oid, builder)); cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_oid)); for (i = 0; i < git_tree_entrycount(tree); i++) { @@ -192,7 +192,7 @@ void test_object_tree_write__removing_and_re_adding_in_treebuilder(void) memset(&blank_oid, 0x0, sizeof(blank_oid)); - cl_git_pass(git_treebuilder_create(&builder, NULL)); + cl_git_pass(git_treebuilder_create(&builder, g_repo, NULL)); cl_assert_equal_i(0, (int)git_treebuilder_entrycount(builder)); @@ -229,7 +229,7 @@ void test_object_tree_write__removing_and_re_adding_in_treebuilder(void) NULL, builder, "apple_extra", &blank_oid, GIT_FILEMODE_BLOB)); cl_assert_equal_i(7, (int)git_treebuilder_entrycount(builder)); - cl_git_pass(git_treebuilder_write(&tree_oid, g_repo, builder)); + cl_git_pass(git_treebuilder_write(&tree_oid, builder)); git_treebuilder_free(builder); @@ -283,7 +283,7 @@ void test_object_tree_write__filtering(void) memset(&blank_oid, 0x0, sizeof(blank_oid)); - cl_git_pass(git_treebuilder_create(&builder, NULL)); + cl_git_pass(git_treebuilder_create(&builder, g_repo, NULL)); for (i = 0; _entries[i].filename; ++i) cl_git_pass(git_treebuilder_insert(NULL, @@ -310,7 +310,7 @@ void test_object_tree_write__filtering(void) cl_assert(git_treebuilder_get(builder, "aardvark") == NULL); cl_assert(git_treebuilder_get(builder, "last") != NULL); - cl_git_pass(git_treebuilder_write(&tree_oid, g_repo, builder)); + cl_git_pass(git_treebuilder_write(&tree_oid, builder)); git_treebuilder_free(builder); @@ -346,13 +346,13 @@ void test_object_tree_write__cruel_paths(void) git_oid_fromstr(&bid, blob_oid); /* create tree */ - cl_git_pass(git_treebuilder_create(&builder, NULL)); + cl_git_pass(git_treebuilder_create(&builder, g_repo, NULL)); for (scan = the_paths; *scan; ++scan) { cl_git_pass(git_treebuilder_insert( NULL, builder, *scan, &bid, GIT_FILEMODE_BLOB)); count++; } - cl_git_pass(git_treebuilder_write(&id, g_repo, builder)); + cl_git_pass(git_treebuilder_write(&id, builder)); git_treebuilder_free(builder); /* check data is correct */ @@ -374,12 +374,12 @@ void test_object_tree_write__cruel_paths(void) git_tree_free(tree); /* let's try longer paths */ - cl_git_pass(git_treebuilder_create(&builder, NULL)); + cl_git_pass(git_treebuilder_create(&builder, g_repo, NULL)); for (scan = the_paths; *scan; ++scan) { cl_git_pass(git_treebuilder_insert( NULL, builder, *scan, &id, GIT_FILEMODE_TREE)); } - cl_git_pass(git_treebuilder_write(&subid, g_repo, builder)); + cl_git_pass(git_treebuilder_write(&subid, builder)); git_treebuilder_free(builder); /* check data is correct */ @@ -400,3 +400,43 @@ void test_object_tree_write__cruel_paths(void) git_tree_free(tree); } + +void test_object_tree_write__protect_filesystems(void) +{ + git_treebuilder *builder; + git_oid bid; + + /* Ensure that (by default) we can write objects with funny names on + * platforms that are not affected. + */ + cl_git_pass(git_treebuilder_create(&builder, g_repo, NULL)); + +#ifndef GIT_WIN32 + cl_git_pass(git_treebuilder_insert(NULL, builder, ".git.", &bid, GIT_FILEMODE_BLOB)); + cl_git_pass(git_treebuilder_insert(NULL, builder, "git~1", &bid, GIT_FILEMODE_BLOB)); +#endif + +#ifndef __APPLE__ + cl_git_pass(git_treebuilder_insert(NULL, builder, ".git\xef\xbb\xbf", &bid, GIT_FILEMODE_BLOB)); + cl_git_pass(git_treebuilder_insert(NULL, builder, ".git\xe2\x80\xad", &bid, GIT_FILEMODE_BLOB)); +#endif + + git_treebuilder_free(builder); + + /* Now turn on core.protectHFS and core.protectNTFS and validate that these + * paths are rejected. + */ + + cl_repo_set_bool(g_repo, "core.protectHFS", true); + cl_repo_set_bool(g_repo, "core.protectNTFS", true); + + cl_git_pass(git_treebuilder_create(&builder, g_repo, NULL)); + + cl_git_fail(git_treebuilder_insert(NULL, builder, ".git.", &bid, GIT_FILEMODE_BLOB)); + cl_git_fail(git_treebuilder_insert(NULL, builder, "git~1", &bid, GIT_FILEMODE_BLOB)); + + cl_git_fail(git_treebuilder_insert(NULL, builder, ".git\xef\xbb\xbf", &bid, GIT_FILEMODE_BLOB)); + cl_git_fail(git_treebuilder_insert(NULL, builder, ".git\xe2\x80\xad", &bid, GIT_FILEMODE_BLOB)); + + git_treebuilder_free(builder); +} diff --git a/tests/repo/iterator.c b/tests/repo/iterator.c index fb5561bc2..0e8793d99 100644 --- a/tests/repo/iterator.c +++ b/tests/repo/iterator.c @@ -427,7 +427,7 @@ static void build_test_tree( git_buf name = GIT_BUF_INIT; va_list arglist; - cl_git_pass(git_treebuilder_create(&builder, NULL)); /* start builder */ + cl_git_pass(git_treebuilder_create(&builder, repo, NULL)); /* start builder */ va_start(arglist, fmt); while (*scan) { @@ -451,7 +451,7 @@ static void build_test_tree( } va_end(arglist); - cl_git_pass(git_treebuilder_write(out, repo, builder)); + cl_git_pass(git_treebuilder_write(out, builder)); git_treebuilder_free(builder); git_buf_free(&name);