diff options
author | Philip Kelley <phkelley@hotmail.com> | 2014-04-19 18:05:31 -0400 |
---|---|---|
committer | Philip Kelley <phkelley@hotmail.com> | 2014-04-19 21:50:44 -0400 |
commit | c2c8161541e54689926ec1f463569d5d1b975503 (patch) | |
tree | d3d1f7096c6449a9d9e19747508ad08cc910d8fe | |
parent | bfc50f83f829afe52c10f615c4f7efa478a83098 (diff) | |
download | libgit2-c2c8161541e54689926ec1f463569d5d1b975503.tar.gz |
Win32: UTF-8 <-> WCHAR conversion overhaul
-rw-r--r-- | src/path.c | 51 | ||||
-rw-r--r-- | src/repository.c | 10 | ||||
-rw-r--r-- | src/transports/winhttp.c | 125 | ||||
-rw-r--r-- | src/unix/posix.h | 1 | ||||
-rw-r--r-- | src/win32/dir.c | 27 | ||||
-rw-r--r-- | src/win32/dir.h | 3 | ||||
-rw-r--r-- | src/win32/error.c | 36 | ||||
-rw-r--r-- | src/win32/findfile.c | 93 | ||||
-rw-r--r-- | src/win32/findfile.h | 11 | ||||
-rw-r--r-- | src/win32/mingw-compat.h | 2 | ||||
-rw-r--r-- | src/win32/posix.h | 11 | ||||
-rw-r--r-- | src/win32/posix_w32.c | 356 | ||||
-rw-r--r-- | src/win32/utf-conv.c | 133 | ||||
-rw-r--r-- | src/win32/utf-conv.h | 76 | ||||
-rw-r--r-- | src/win32/w32_util.c | 69 | ||||
-rw-r--r-- | src/win32/w32_util.h | 31 | ||||
-rw-r--r-- | tests/clar_libgit2.c | 53 | ||||
-rw-r--r-- | tests/core/env.c | 2 |
18 files changed, 659 insertions, 431 deletions
diff --git a/src/path.c b/src/path.c index a990b005f..b2845466b 100644 --- a/src/path.c +++ b/src/path.c @@ -9,6 +9,7 @@ #include "posix.h" #ifdef GIT_WIN32 #include "win32/posix.h" +#include "win32/w32_util.h" #else #include <dirent.h> #endif @@ -486,33 +487,33 @@ bool git_path_isfile(const char *path) bool git_path_is_empty_dir(const char *path) { - HANDLE hFind = INVALID_HANDLE_VALUE; - git_win32_path wbuf; - int wbufsz; - WIN32_FIND_DATAW ffd; - bool retval = true; - - if (!git_path_isdir(path)) - return false; - - wbufsz = git_win32_path_from_c(wbuf, path); - if (!wbufsz || wbufsz + 2 > GIT_WIN_PATH_UTF16) - return false; - memcpy(&wbuf[wbufsz - 1], L"\\*", 3 * sizeof(wchar_t)); - - hFind = FindFirstFileW(wbuf, &ffd); - if (INVALID_HANDLE_VALUE == hFind) - return false; - - do { - if (!git_path_is_dot_or_dotdotW(ffd.cFileName)) { - retval = false; - break; + git_win32_path filter_w; + bool empty = false; + + if (git_win32__findfirstfile_filter(filter_w, path)) { + WIN32_FIND_DATAW findData; + HANDLE hFind = FindFirstFileW(filter_w, &findData); + + /* If the find handle was created successfully, then it's a directory */ + if (INVALID_HANDLE_VALUE != hFind) { + empty = true; + + do { + /* Allow the enumeration to return . and .. and still be considered + * empty. In the special case of drive roots (i.e. C:\) where . and + * .. do not occur, we can still consider the path to be an empty + * directory if there's nothing there. */ + if (!git_path_is_dot_or_dotdotW(findData.cFileName)) { + empty = false; + break; + } + } while (FindNextFileW(hFind, &findData)); + + FindClose(hFind); } - } while (FindNextFileW(hFind, &ffd) != 0); + } - FindClose(hFind); - return retval; + return empty; } #else diff --git a/src/repository.c b/src/repository.c index 6b2705bfa..8daa04d5d 100644 --- a/src/repository.c +++ b/src/repository.c @@ -27,6 +27,10 @@ #include "merge.h" #include "diff_driver.h" +#ifdef GIT_WIN32 +# include "win32/w32_util.h" +#endif + #define GIT_FILE_CONTENT_PREFIX "gitdir:" #define GIT_BRANCH_MASTER "master" @@ -1149,7 +1153,7 @@ static int repo_write_template( #ifdef GIT_WIN32 if (!error && hidden) { - if (p_hide_directory__w32(path.ptr) < 0) + if (git_win32__sethidden(path.ptr) < 0) error = -1; } #else @@ -1234,8 +1238,8 @@ static int repo_init_structure( /* Hide the ".git" directory */ #ifdef GIT_WIN32 if ((opts->flags & GIT_REPOSITORY_INIT__HAS_DOTGIT) != 0) { - if (p_hide_directory__w32(repo_dir) < 0) { - giterr_set(GITERR_REPOSITORY, + if (git_win32__sethidden(repo_dir) < 0) { + giterr_set(GITERR_OS, "Failed to mark Git repository folder as hidden"); return -1; } diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index 7ad2a1636..bd9509cd4 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -91,7 +91,7 @@ static int apply_basic_credential(HINTERNET request, git_cred *cred) git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; git_buf buf = GIT_BUF_INIT, raw = GIT_BUF_INIT; wchar_t *wide = NULL; - int error = -1, wide_len = 0; + int error = -1, wide_len; git_buf_printf(&raw, "%s:%s", c->username, c->password); @@ -100,21 +100,7 @@ static int apply_basic_credential(HINTERNET request, git_cred *cred) git_buf_put_base64(&buf, git_buf_cstr(&raw), raw.size) < 0) goto on_error; - wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, - git_buf_cstr(&buf), -1, NULL, 0); - - if (!wide_len) { - giterr_set(GITERR_OS, "Failed to measure string for wide conversion"); - goto on_error; - } - - wide = git__malloc(wide_len * sizeof(wchar_t)); - - if (!wide) - goto on_error; - - if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, - git_buf_cstr(&buf), -1, wide, wide_len)) { + if ((wide_len = git__utf8_to_16_alloc(&wide, git_buf_cstr(&buf))) < 0) { giterr_set(GITERR_OS, "Failed to convert string to wide form"); goto on_error; } @@ -171,23 +157,11 @@ static int fallback_cred_acquire_cb( /* If the target URI supports integrated Windows authentication * as an authentication mechanism */ if (GIT_CREDTYPE_DEFAULT & allowed_types) { - LPWSTR wide_url; - DWORD wide_len; + wchar_t *wide_url; /* Convert URL to wide characters */ - wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, url, -1, NULL, 0); - - if (!wide_len) { - giterr_set(GITERR_OS, "Failed to measure string for wide conversion"); - return -1; - } - - wide_url = git__malloc(wide_len * sizeof(WCHAR)); - GITERR_CHECK_ALLOC(wide_url); - - if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, url, -1, wide_url, wide_len)) { + if (git__utf8_to_16_alloc(&wide_url, url) < 0) { giterr_set(GITERR_OS, "Failed to convert string to wide form"); - git__free(wide_url); return -1; } @@ -232,7 +206,7 @@ static int winhttp_stream_connect(winhttp_stream *s) wchar_t ct[MAX_CONTENT_TYPE_LEN]; wchar_t *types[] = { L"*/*", NULL }; BOOL peerdist = FALSE; - int error = -1, wide_len; + int error = -1; unsigned long disable_redirects = WINHTTP_DISABLE_REDIRECTS; /* Prepare URL */ @@ -242,21 +216,7 @@ static int winhttp_stream_connect(winhttp_stream *s) return -1; /* Convert URL to wide characters */ - wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, - git_buf_cstr(&buf), -1, NULL, 0); - - if (!wide_len) { - giterr_set(GITERR_OS, "Failed to measure string for wide conversion"); - goto on_error; - } - - s->request_uri = git__malloc(wide_len * sizeof(wchar_t)); - - if (!s->request_uri) - goto on_error; - - if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, - git_buf_cstr(&buf), -1, s->request_uri, wide_len)) { + if (git__utf8_to_16_alloc(&s->request_uri, git_buf_cstr(&buf)) < 0) { giterr_set(GITERR_OS, "Failed to convert string to wide form"); goto on_error; } @@ -285,30 +245,17 @@ static int winhttp_stream_connect(winhttp_stream *s) wchar_t *proxy_wide; /* Convert URL to wide characters */ - wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, - proxy_url, -1, NULL, 0); - - if (!wide_len) { - giterr_set(GITERR_OS, "Failed to measure string for wide conversion"); - goto on_error; - } - - proxy_wide = git__malloc(wide_len * sizeof(wchar_t)); - - if (!proxy_wide) - goto on_error; + int proxy_wide_len = git__utf8_to_16_alloc(&proxy_wide, proxy_url); - if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, - proxy_url, -1, proxy_wide, wide_len)) { + if (proxy_wide_len < 0) { giterr_set(GITERR_OS, "Failed to convert string to wide form"); - git__free(proxy_wide); goto on_error; } /* Strip any trailing forward slash on the proxy URL; * WinHTTP doesn't like it if one is present */ - if (wide_len > 1 && L'/' == proxy_wide[wide_len - 2]) - proxy_wide[wide_len - 2] = L'\0'; + if (proxy_wide_len > 1 && L'/' == proxy_wide[proxy_wide_len - 2]) + proxy_wide[proxy_wide_len - 2] = L'\0'; proxy_info.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY; proxy_info.lpszProxy = proxy_wide; @@ -359,7 +306,10 @@ static int winhttp_stream_connect(winhttp_stream *s) s->service) < 0) goto on_error; - git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)); + if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)) < 0) { + giterr_set(GITERR_OS, "Failed to convert content-type to wide characters"); + goto on_error; + } if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { @@ -373,7 +323,10 @@ static int winhttp_stream_connect(winhttp_stream *s) s->service) < 0) goto on_error; - git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)); + if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)) < 0) { + giterr_set(GITERR_OS, "Failed to convert accept header to wide characters"); + goto on_error; + } if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { @@ -506,16 +459,20 @@ static int winhttp_connect( const char *url) { wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")"; - git_win32_path host; + wchar_t *wide_host; int32_t port; const char *default_port = "80"; + int error = -1; /* Prepare port */ if (git__strtol32(&port, t->connection_data.port, NULL, 10) < 0) return -1; /* Prepare host */ - git_win32_path_from_c(host, t->connection_data.host); + if (git__utf8_to_16_alloc(&wide_host, t->connection_data.host) < 0) { + giterr_set(GITERR_OS, "Unable to convert host to wide characters"); + return -1; + } /* Establish session */ t->session = WinHttpOpen( @@ -527,22 +484,27 @@ static int winhttp_connect( if (!t->session) { giterr_set(GITERR_OS, "Failed to init WinHTTP"); - return -1; + goto on_error; } /* Establish connection */ t->connection = WinHttpConnect( t->session, - host, + wide_host, (INTERNET_PORT) port, 0); if (!t->connection) { giterr_set(GITERR_OS, "Failed to connect to host"); - return -1; + goto on_error; } - return 0; + error = 0; + +on_error: + git__free(wide_host); + + return error; } static int winhttp_stream_read( @@ -693,7 +655,6 @@ replay: } location = git__malloc(location_length); - location8 = git__malloc(location_length); GITERR_CHECK_ALLOC(location); if (!WinHttpQueryHeaders(s->request, @@ -706,7 +667,14 @@ replay: git__free(location); return -1; } - git__utf16_to_8(location8, location_length, location); + + /* Convert the Location header to UTF-8 */ + if (git__utf16_to_8_alloc(&location8, location) < 0) { + giterr_set(GITERR_OS, "Failed to convert Location header to UTF-8"); + git__free(location); + return -1; + } + git__free(location); /* Replay the request */ @@ -716,8 +684,11 @@ replay: if (!git__prefixcmp_icase(location8, prefix_https)) { /* Upgrade to secure connection; disconnect and start over */ - if (gitno_connection_data_from_url(&t->connection_data, location8, s->service_url) < 0) + if (gitno_connection_data_from_url(&t->connection_data, location8, s->service_url) < 0) { + git__free(location8); return -1; + } + winhttp_connect(t, location8); } @@ -778,7 +749,11 @@ replay: else snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service); - git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8); + if (git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8) < 0) { + giterr_set(GITERR_OS, "Failed to convert expected content-type to wide characters"); + return -1; + } + content_type_length = sizeof(content_type); if (!WinHttpQueryHeaders(s->request, diff --git a/src/unix/posix.h b/src/unix/posix.h index 9c9f837b9..1e41bcf18 100644 --- a/src/unix/posix.h +++ b/src/unix/posix.h @@ -28,7 +28,6 @@ char *p_realpath(const char *, char *); #define p_vsnprintf(b, c, f, a) vsnprintf(b, c, f, a) #define p_snprintf(b, c, f, ...) snprintf(b, c, f, __VA_ARGS__) #define p_mkstemp(p) mkstemp(p) -#define p_setenv(n,v,o) setenv(n,v,o) #define p_inet_pton(a, b, c) inet_pton(a, b, c) /* see win32/posix.h for explanation about why this exists */ diff --git a/src/win32/dir.c b/src/win32/dir.c index f7859b73f..c7427ea54 100644 --- a/src/win32/dir.c +++ b/src/win32/dir.c @@ -7,29 +7,13 @@ #define GIT__WIN32_NO_WRAP_DIR #include "posix.h" -static int init_filter(char *filter, size_t n, const char *dir) -{ - size_t len = strlen(dir); - - if (len+3 >= n) - return 0; - - strcpy(filter, dir); - if (len && dir[len-1] != '/') - strcat(filter, "/"); - strcat(filter, "*"); - - return 1; -} - git__DIR *git__opendir(const char *dir) { - git_win32_path_as_utf8 filter; git_win32_path filter_w; git__DIR *new = NULL; size_t dirlen; - if (!dir || !init_filter(filter, sizeof(filter), dir)) + if (!dir || !git_win32__findfirstfile_filter(filter_w, dir)) return NULL; dirlen = strlen(dir); @@ -39,7 +23,6 @@ git__DIR *git__opendir(const char *dir) return NULL; memcpy(new->dir, dir, dirlen); - git_win32_path_from_c(filter_w, filter); new->h = FindFirstFileW(filter_w, &new->f); if (new->h == INVALID_HANDLE_VALUE) { @@ -72,10 +55,10 @@ int git__readdir_ext( return -1; } - if (wcslen(d->f.cFileName) >= sizeof(entry->d_name)) + /* Convert the path to UTF-8 */ + if (git_win32_path_to_utf8(entry->d_name, d->f.cFileName) < 0) return -1; - git_win32_path_to_c(entry->d_name, d->f.cFileName); entry->d_ino = 0; *result = entry; @@ -96,7 +79,6 @@ struct git__dirent *git__readdir(git__DIR *d) void git__rewinddir(git__DIR *d) { - git_win32_path_as_utf8 filter; git_win32_path filter_w; if (!d) @@ -108,10 +90,9 @@ void git__rewinddir(git__DIR *d) d->first = 0; } - if (!init_filter(filter, sizeof(filter), d->dir)) + if (!git_win32__findfirstfile_filter(filter_w, d->dir)) return; - git_win32_path_from_c(filter_w, filter); d->h = FindFirstFileW(filter_w, &d->f); if (d->h == INVALID_HANDLE_VALUE) diff --git a/src/win32/dir.h b/src/win32/dir.h index 24d48f6ba..bef39d774 100644 --- a/src/win32/dir.h +++ b/src/win32/dir.h @@ -8,10 +8,11 @@ #define INCLUDE_dir_h__ #include "common.h" +#include "w32_util.h" struct git__dirent { int d_ino; - git_win32_path_as_utf8 d_name; + git_win32_utf8_path d_name; }; typedef struct { diff --git a/src/win32/error.c b/src/win32/error.c index bc598ae32..6b450093f 100644 --- a/src/win32/error.c +++ b/src/win32/error.c @@ -7,21 +7,17 @@ #include "common.h" #include "error.h" +#include "utf-conv.h" #ifdef GIT_WINHTTP # include <winhttp.h> #endif -#ifndef WC_ERR_INVALID_CHARS -#define WC_ERR_INVALID_CHARS 0x80 -#endif - char *git_win32_get_error_message(DWORD error_code) { LPWSTR lpMsgBuf = NULL; HMODULE hModule = NULL; char *utf8_msg = NULL; - int utf8_size; DWORD dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS; @@ -45,33 +41,11 @@ char *git_win32_get_error_message(DWORD error_code) if (FormatMessageW(dwFlags, hModule, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&lpMsgBuf, 0, NULL)) { + /* Convert the message to UTF-8. If this fails, we will + * return NULL, which is a condition expected by the caller */ + if (git__utf16_to_8_alloc(&utf8_msg, lpMsgBuf) < 0) + utf8_msg = NULL; - /* Invalid code point check supported on Vista+ only */ - if (git_has_win32_version(6, 0, 0)) - dwFlags = WC_ERR_INVALID_CHARS; - else - dwFlags = 0; - - utf8_size = WideCharToMultiByte(CP_UTF8, dwFlags, - lpMsgBuf, -1, NULL, 0, NULL, NULL); - - if (!utf8_size) { - assert(0); - goto on_error; - } - - utf8_msg = git__malloc(utf8_size); - - if (!utf8_msg) - goto on_error; - - if (!WideCharToMultiByte(CP_UTF8, dwFlags, - lpMsgBuf, -1, utf8_msg, utf8_size, NULL, NULL)) { - git__free(utf8_msg); - goto on_error; - } - -on_error: LocalFree(lpMsgBuf); } diff --git a/src/win32/findfile.c b/src/win32/findfile.c index a9e812e28..bd06d9232 100644 --- a/src/win32/findfile.c +++ b/src/win32/findfile.c @@ -17,54 +17,34 @@ #define REG_MSYSGIT_INSTALL L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1" #endif -int git_win32__expand_path(struct git_win32__path *s_root, const wchar_t *templ) -{ - s_root->len = ExpandEnvironmentStringsW(templ, s_root->path, MAX_PATH); - return s_root->len ? 0 : -1; -} +typedef struct { + git_win32_path path; + DWORD len; +} _findfile_path; -static int win32_path_to_8(git_buf *path_utf8, const wchar_t *path) +static int git_win32__expand_path(_findfile_path *dest, const wchar_t *src) { - char temp_utf8[GIT_PATH_MAX]; + dest->len = ExpandEnvironmentStringsW(src, dest->path, ARRAY_SIZE(dest->path)); - git__utf16_to_8(temp_utf8, GIT_PATH_MAX, path); - git_path_mkposix(temp_utf8); + if (!dest->len || dest->len > ARRAY_SIZE(dest->path)) + return -1; - return git_buf_sets(path_utf8, temp_utf8); + return 0; } -int git_win32__find_file( - git_buf *path, const struct git_win32__path *root, const char *filename) +static int win32_path_to_8(git_buf *dest, const wchar_t *src) { - size_t len, alloc_len; - wchar_t *file_utf16 = NULL; - - if (!root || !filename || (len = strlen(filename)) == 0) - return GIT_ENOTFOUND; - - /* allocate space for wchar_t path to file */ - alloc_len = root->len + len + 2; - file_utf16 = git__calloc(alloc_len, sizeof(wchar_t)); - GITERR_CHECK_ALLOC(file_utf16); + git_win32_utf8_path utf8_path; - /* append root + '\\' + filename as wchar_t */ - memcpy(file_utf16, root->path, root->len * sizeof(wchar_t)); - - if (*filename == '/' || *filename == '\\') - filename++; - - git__utf8_to_16(file_utf16 + root->len - 1, alloc_len - root->len, filename); - - /* check access */ - if (_waccess(file_utf16, F_OK) < 0) { - git__free(file_utf16); - return GIT_ENOTFOUND; + if (git_win32_path_to_utf8(utf8_path, src) < 0) { + giterr_set(GITERR_OS, "Unable to convert path to UTF-8"); + return -1; } - win32_path_to_8(path, file_utf16); - git__free(file_utf16); + /* Convert backslashes to forward slashes */ + git_path_mkposix(utf8_path); - return 0; + return git_buf_sets(dest, utf8_path); } static wchar_t* win32_walkpath(wchar_t *path, wchar_t *buf, size_t buflen) @@ -89,7 +69,7 @@ static wchar_t* win32_walkpath(wchar_t *path, wchar_t *buf, size_t buflen) static int win32_find_git_in_path(git_buf *buf, const wchar_t *gitexe, const wchar_t *subdir) { wchar_t *env = _wgetenv(L"PATH"), lastch; - struct git_win32__path root; + _findfile_path root; size_t gitexe_len = wcslen(gitexe); if (!env) @@ -122,43 +102,44 @@ static int win32_find_git_in_path(git_buf *buf, const wchar_t *gitexe, const wch } static int win32_find_git_in_registry( - git_buf *buf, const HKEY hieve, const wchar_t *key, const wchar_t *subdir) + git_buf *buf, const HKEY hive, const wchar_t *key, const wchar_t *subdir) { HKEY hKey; - DWORD dwType = REG_SZ; - struct git_win32__path path16; + int error = GIT_ENOTFOUND; assert(buf); - path16.len = MAX_PATH; + if (!RegOpenKeyExW(hive, key, 0, KEY_READ, &hKey)) { + DWORD dwType, cbData; + git_win32_path path; - if (RegOpenKeyExW(hieve, key, 0, KEY_READ, &hKey) == ERROR_SUCCESS) { - if (RegQueryValueExW(hKey, L"InstallLocation", NULL, &dwType, - (LPBYTE)&path16.path, &path16.len) == ERROR_SUCCESS) - { - /* InstallLocation points to the root of the git directory */ + /* Ensure that the buffer is big enough to have the suffix attached + * after we receive the result. */ + cbData = (DWORD)(sizeof(path) - wcslen(subdir) * sizeof(wchar_t)); - if (path16.len + 4 > MAX_PATH) { /* 4 = wcslen(L"etc\\") */ - giterr_set(GITERR_OS, "Cannot locate git - path too long"); - return -1; - } + /* InstallLocation points to the root of the git directory */ + if (!RegQueryValueExW(hKey, L"InstallLocation", NULL, &dwType, (LPBYTE)path, &cbData) && + REG_SZ == dwType) { - wcscat(path16.path, subdir); - path16.len += 4; + /* Append the suffix */ + wcscat(path, subdir); - win32_path_to_8(buf, path16.path); + /* Convert to UTF-8, with forward slashes, and output the path + * to the provided buffer */ + if (!win32_path_to_8(buf, path)) + error = 0; } RegCloseKey(hKey); } - return path16.len ? 0 : GIT_ENOTFOUND; + return error; } static int win32_find_existing_dirs( git_buf *out, const wchar_t *tmpl[]) { - struct git_win32__path path16; + _findfile_path path16; git_buf buf = GIT_BUF_INIT; git_buf_clear(out); diff --git a/src/win32/findfile.h b/src/win32/findfile.h index 11bf7e620..a50319b9a 100644 --- a/src/win32/findfile.h +++ b/src/win32/findfile.h @@ -8,17 +8,6 @@ #ifndef INCLUDE_git_findfile_h__ #define INCLUDE_git_findfile_h__ -struct git_win32__path { - wchar_t path[MAX_PATH]; - DWORD len; -}; - -extern int git_win32__expand_path( - struct git_win32__path *s_root, const wchar_t *templ); - -extern int git_win32__find_file( - git_buf *path, const struct git_win32__path *root, const char *filename); - extern int git_win32__find_system_dirs(git_buf *out, const wchar_t *subpath); extern int git_win32__find_global_dirs(git_buf *out); extern int git_win32__find_xdg_dirs(git_buf *out); diff --git a/src/win32/mingw-compat.h b/src/win32/mingw-compat.h index 7b97b48db..8f51d6f5a 100644 --- a/src/win32/mingw-compat.h +++ b/src/win32/mingw-compat.h @@ -11,7 +11,9 @@ /* use a 64-bit file offset type */ # define lseek _lseeki64 +# undef stat # define stat _stati64 +# undef fstat # define fstat _fstati64 /* stat: file mode type testing macros */ diff --git a/src/win32/posix.h b/src/win32/posix.h index 24cba23e0..e5a32b510 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -27,24 +27,15 @@ GIT_INLINE(int) p_link(const char *old, const char *new) return -1; } -GIT_INLINE(int) p_mkdir(const char *path, mode_t mode) -{ - git_win32_path buf; - GIT_UNUSED(mode); - git_win32_path_from_c(buf, path); - return _wmkdir(buf); -} - +extern int p_mkdir(const char *path, mode_t mode); extern int p_unlink(const char *path); extern int p_lstat(const char *file_name, struct stat *buf); extern int p_readlink(const char *link, char *target, size_t target_len); extern int p_symlink(const char *old, const char *new); -extern int p_hide_directory__w32(const char *path); extern char *p_realpath(const char *orig_path, char *buffer); extern int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr); extern int p_snprintf(char *buffer, size_t count, const char *format, ...) GIT_FORMAT_PRINTF(3, 4); extern int p_mkstemp(char *tmp_path); -extern int p_setenv(const char* name, const char* value, int overwrite); extern int p_stat(const char* path, struct stat* buf); extern int p_chdir(const char* path); extern int p_chmod(const char* path, mode_t mode); diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 6f2931880..868be6017 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -14,12 +14,59 @@ #include <fcntl.h> #include <ws2tcpip.h> +#if defined(__MINGW32__) +# define FILE_NAME_NORMALIZED 0 +#endif + +/* 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_mkdir(const char *path, mode_t mode) +{ + git_win32_path buf; + + GIT_UNUSED(mode); + + if (utf8_to_16_with_errno(buf, path) < 0) + return -1; + + return _wmkdir(buf); +} + int p_unlink(const char *path) { git_win32_path buf; - git_win32_path_from_c(buf, path); - _wchmod(buf, 0666); - return _wunlink(buf); + int error; + + if (utf8_to_16_with_errno(buf, path) < 0) + return -1; + + error = _wunlink(buf); + + /* If the file could not be deleted because it was + * read-only, clear the bit and try again */ + if (-1 == error && EACCES == errno) { + _wchmod(buf, 0666); + error = _wunlink(buf); + } + + return error; } int p_fsync(int fd) @@ -63,7 +110,8 @@ static int do_lstat( wchar_t lastch; int flen; - flen = git_win32_path_from_c(fbuf, file_name); + if ((flen = utf8_to_16_with_errno(fbuf, file_name)) < 0) + return -1; /* truncate trailing slashes */ for (; flen > 0; --flen) { @@ -109,12 +157,9 @@ static int do_lstat( * the length of the path pointed to, which we expect everywhere else */ if (S_ISLNK(fMode)) { - git_win32_path_as_utf8 target; - int readlink_result; + git_win32_utf8_path target; - readlink_result = p_readlink(file_name, target, sizeof(target)); - - if (readlink_result == -1) + if (p_readlink(file_name, target, GIT_WIN_PATH_UTF8) == -1) return -1; buf->st_size = strlen(target); @@ -160,6 +205,28 @@ int p_lstat_posixly(const char *filename, struct stat *buf) return do_lstat(filename, buf, 1); } +/* + * Returns the address of the GetFinalPathNameByHandleW function. + * This function is available on Windows Vista and higher. + */ +static PFGetFinalPathNameByHandleW get_fpnbyhandle(void) +{ + static PFGetFinalPathNameByHandleW pFunc = NULL; + PFGetFinalPathNameByHandleW toReturn = pFunc; + + if (!toReturn) { + HMODULE hModule = GetModuleHandleW(L"kernel32"); + + if (hModule) + toReturn = (PFGetFinalPathNameByHandleW)GetProcAddress(hModule, "GetFinalPathNameByHandleW"); + + pFunc = toReturn; + } + + assert(toReturn); + + return toReturn; +} /* * Parts of the The p_readlink function are heavily inspired by the php @@ -171,87 +238,62 @@ int p_lstat_posixly(const char *filename, struct stat *buf) */ int p_readlink(const char *link, char *target, size_t target_len) { - typedef DWORD (WINAPI *fpath_func)(HANDLE, LPWSTR, DWORD, DWORD); - static fpath_func pGetFinalPath = NULL; - HANDLE hFile; - DWORD dwRet; + static const wchar_t prefix[] = L"\\\\?\\"; + PFGetFinalPathNameByHandleW pgfp = get_fpnbyhandle(); + HANDLE hFile = NULL; + wchar_t *target_w = NULL; + bool trim_prefix; git_win32_path link_w; - wchar_t* target_w; - int error = 0; + DWORD dwChars, dwLastError; + int error = -1; - assert(link && target && target_len > 0); + /* Check that we found the function, and convert to UTF-16 */ + if (!pgfp || utf8_to_16_with_errno(link_w, link) < 0) + goto on_error; - /* - * Try to load the pointer to pGetFinalPath dynamically, because - * it is not available in platforms older than Vista - */ - if (pGetFinalPath == NULL) { - HMODULE module = GetModuleHandle("kernel32"); + /* Use FILE_FLAG_BACKUP_SEMANTICS so we can open a directory. Do not + * specify FILE_FLAG_OPEN_REPARSE_POINT; we want to open a handle to the + * target of the link. */ + hFile = CreateFileW(link_w, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); - if (module != NULL) - pGetFinalPath = (fpath_func)GetProcAddress(module, "GetFinalPathNameByHandleW"); + if (hFile == INVALID_HANDLE_VALUE) + goto on_error; - if (pGetFinalPath == NULL) { - giterr_set(GITERR_OS, - "'GetFinalPathNameByHandleW' is not available in this platform"); - return -1; - } - } + /* Find out how large the buffer should be to hold the result */ + if (!(dwChars = pgfp(hFile, NULL, 0, FILE_NAME_NORMALIZED))) + goto on_error; - git_win32_path_from_c(link_w, link); + if (!(target_w = git__malloc(dwChars * sizeof(wchar_t)))) + goto on_error; - hFile = CreateFileW(link_w, // file to open - GENERIC_READ, // open for reading - FILE_SHARE_READ, // share for reading - NULL, // default security - OPEN_EXISTING, // existing file only - FILE_FLAG_BACKUP_SEMANTICS, // normal file - NULL); // no attr. template + /* Call a second time */ + dwChars = pgfp(hFile, target_w, dwChars, FILE_NAME_NORMALIZED); - if (hFile == INVALID_HANDLE_VALUE) { - giterr_set(GITERR_OS, "Cannot open '%s' for reading", link); - return -1; - } + if (!dwChars) + goto on_error; - target_w = (wchar_t*)git__malloc(target_len * sizeof(wchar_t)); - GITERR_CHECK_ALLOC(target_w); - - dwRet = pGetFinalPath(hFile, target_w, (DWORD)target_len, 0x0); - if (dwRet == 0 || - dwRet >= target_len || - !WideCharToMultiByte(CP_UTF8, 0, target_w, -1, target, - (int)(target_len * sizeof(char)), NULL, NULL)) - error = -1; - - git__free(target_w); - CloseHandle(hFile); - - if (error) - return error; - - /* Skip first 4 characters if they are "\\?\" */ - if (dwRet > 4 && - target[0] == '\\' && target[1] == '\\' && - target[2] == '?' && target[3] == '\\') - { - unsigned int offset = 4; - dwRet -= 4; - - /* \??\UNC\ */ - if (dwRet > 7 && - target[4] == 'U' && target[5] == 'N' && target[6] == 'C') - { - offset += 2; - dwRet -= 2; - target[offset] = '\\'; - } + /* Do we need to trim off a \\?\ from the start of the path? */ + trim_prefix = (dwChars >= ARRAY_SIZE(prefix)) && + !wcsncmp(prefix, target_w, ARRAY_SIZE(prefix)); - memmove(target, target + offset, dwRet); - } + /* Convert the result to UTF-8 */ + if (git__utf16_to_8(target, target_len, trim_prefix ? target_w + 4 : target_w) < 0) + goto on_error; + + error = 0; - target[dwRet] = '\0'; +on_error: + dwLastError = GetLastError(); - return dwRet; + if (hFile && INVALID_HANDLE_VALUE != hFile) + CloseHandle(hFile); + + if (target_w) + git__free(target_w); + + SetLastError(dwLastError); + return error; } int p_symlink(const char *old, const char *new) @@ -267,7 +309,8 @@ int p_open(const char *path, int flags, ...) git_win32_path buf; mode_t mode = 0; - git_win32_path_from_c(buf, path); + if (utf8_to_16_with_errno(buf, path) < 0) + return -1; if (flags & O_CREAT) { va_list arg_list; @@ -283,33 +326,39 @@ int p_open(const char *path, int flags, ...) int p_creat(const char *path, mode_t mode) { git_win32_path buf; - git_win32_path_from_c(buf, path); + + if (utf8_to_16_with_errno(buf, path) < 0) + return -1; + return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, mode); } int p_getcwd(char *buffer_out, size_t size) { - int ret; - wchar_t *buf; + git_win32_path buf; + wchar_t *cwd = _wgetcwd(buf, GIT_WIN_PATH_UTF16); - if ((size_t)((int)size) != size) + if (!cwd) return -1; - buf = (wchar_t*)git__malloc(sizeof(wchar_t) * (int)size); - GITERR_CHECK_ALLOC(buf); + /* Convert the working directory back to UTF-8 */ + if (git__utf16_to_8(buffer_out, size, cwd) < 0) { + DWORD code = GetLastError(); - _wgetcwd(buf, (int)size); + if (code == ERROR_INSUFFICIENT_BUFFER) + errno = ERANGE; + else + errno = EINVAL; - ret = WideCharToMultiByte( - CP_UTF8, 0, buf, -1, buffer_out, (int)size, NULL, NULL); + return -1; + } - git__free(buf); - return !ret ? -1 : 0; + return 0; } int p_stat(const char* path, struct stat* buf) { - git_win32_path_as_utf8 target; + git_win32_utf8_path target; int error = 0; error = do_lstat(path, buf, 0); @@ -317,7 +366,7 @@ int p_stat(const char* path, struct stat* buf) /* We need not do this in a loop to unwind chains of symlinks since * p_readlink calls GetFinalPathNameByHandle which does it for us. */ if (error >= 0 && S_ISLNK(buf->st_mode) && - (error = p_readlink(path, target, sizeof(target))) >= 0) + (error = p_readlink(path, target, GIT_WIN_PATH_UTF8)) >= 0) error = do_lstat(target, buf, 0); return error; @@ -326,82 +375,93 @@ int p_stat(const char* path, struct stat* buf) int p_chdir(const char* path) { git_win32_path buf; - git_win32_path_from_c(buf, path); + + if (utf8_to_16_with_errno(buf, path) < 0) + return -1; + return _wchdir(buf); } int p_chmod(const char* path, mode_t mode) { git_win32_path buf; - git_win32_path_from_c(buf, path); + + if (utf8_to_16_with_errno(buf, path) < 0) + return -1; + return _wchmod(buf, mode); } int p_rmdir(const char* path) { - int error; git_win32_path buf; - git_win32_path_from_c(buf, path); + int error; + + if (utf8_to_16_with_errno(buf, path) < 0) + return -1; error = _wrmdir(buf); - /* _wrmdir() is documented to return EACCES if "A program has an open - * handle to the directory." This sounds like what everybody else calls - * EBUSY. Let's convert appropriate error codes. - */ - if (GetLastError() == ERROR_SHARING_VIOLATION) - errno = EBUSY; + if (-1 == error) { + switch (GetLastError()) { + /* _wrmdir() is documented to return EACCES if "A program has an open + * handle to the directory." This sounds like what everybody else calls + * EBUSY. Let's convert appropriate error codes. + */ + case ERROR_SHARING_VIOLATION: + errno = EBUSY; + break; - return error; -} + /* This error can be returned when trying to rmdir an extant file. */ + case ERROR_DIRECTORY: + errno = ENOTDIR; + break; + } + } -int p_hide_directory__w32(const char *path) -{ - git_win32_path buf; - git_win32_path_from_c(buf, path); - return (SetFileAttributesW(buf, FILE_ATTRIBUTE_HIDDEN) != 0) ? 0 : -1; + return error; } char *p_realpath(const char *orig_path, char *buffer) { - int ret; - git_win32_path orig_path_w; - git_win32_path buffer_w; + git_win32_path orig_path_w, buffer_w; - git_win32_path_from_c(orig_path_w, orig_path); + if (utf8_to_16_with_errno(orig_path_w, orig_path) < 0) + return NULL; - /* Implicitly use GetCurrentDirectory which can be a threading issue */ - ret = GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL); + /* Note that if the path provided is a relative path, then the current directory + * is used to resolve the path -- which is a concurrency issue because the current + * directory is a process-wide variable. */ + if (!GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL)) { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) + errno = ENAMETOOLONG; + else + errno = EINVAL; - /* According to MSDN, a return value equals to zero means a failure. */ - if (ret == 0 || ret > GIT_WIN_PATH_UTF16) - buffer = NULL; + return NULL; + } - else if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) { - buffer = NULL; + /* The path must exist. */ + if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(buffer_w)) { errno = ENOENT; + return NULL; } - else if (buffer == NULL) { - int buffer_sz = WideCharToMultiByte( - CP_UTF8, 0, buffer_w, -1, NULL, 0, NULL, NULL); - - if (!buffer_sz || - !(buffer = (char *)git__malloc(buffer_sz)) || - !WideCharToMultiByte( - CP_UTF8, 0, buffer_w, -1, buffer, buffer_sz, NULL, NULL)) - { - git__free(buffer); - buffer = 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; } - else if (!WideCharToMultiByte( - CP_UTF8, 0, buffer_w, -1, buffer, GIT_PATH_MAX, NULL, NULL)) - buffer = NULL; - - if (buffer) - git_path_mkposix(buffer); + /* Convert backslashes to forward slashes */ + git_path_mkposix(buffer); return buffer; } @@ -433,8 +493,6 @@ int p_snprintf(char *buffer, size_t count, const char *format, ...) return r; } -extern int p_creat(const char *path, mode_t mode); - int p_mkstemp(char *tmp_path) { #if defined(_MSC_VER) @@ -448,18 +506,13 @@ int p_mkstemp(char *tmp_path) return p_creat(tmp_path, 0744); //-V536 } -int p_setenv(const char* name, const char* value, int overwrite) -{ - if (overwrite != 1) - return -1; - - return (SetEnvironmentVariableA(name, value) == 0 ? -1 : 0); -} - int p_access(const char* path, mode_t mode) { git_win32_path buf; - git_win32_path_from_c(buf, path); + + if (utf8_to_16_with_errno(buf, path) < 0) + return -1; + return _waccess(buf, mode); } @@ -471,8 +524,9 @@ int p_rename(const char *from, const char *to) int rename_succeeded; int error; - git_win32_path_from_c(wfrom, from); - git_win32_path_from_c(wto, to); + if (utf8_to_16_with_errno(wfrom, from) < 0 || + utf8_to_16_with_errno(wto, to) < 0) + return -1; /* wait up to 50ms if file is locked by another thread or process */ rename_tries = 0; diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c index a96385f10..fe94701a8 100644 --- a/src/win32/utf-conv.c +++ b/src/win32/utf-conv.c @@ -8,12 +8,139 @@ #include "common.h" #include "utf-conv.h" -int git__utf8_to_16(wchar_t * dest, size_t dest_size, const char *src) +#ifndef WC_ERR_INVALID_CHARS +#define WC_ERR_INVALID_CHARS 0x80 +#endif + +GIT_INLINE(DWORD) get_wc_flags(void) { - return MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, (int)dest_size); + static char inited = 0; + static DWORD flags; + + /* Invalid code point check supported on Vista+ only */ + if (!inited) { + flags = git_has_win32_version(6, 0, 0) ? WC_ERR_INVALID_CHARS : 0; + inited = 1; + } + + return flags; } +/** + * Converts a UTF-8 string to wide characters. + * + * @param dest The buffer to receive the wide string. + * @param dest_size The size of the buffer, in characters. + * @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 + */ +int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src) +{ + /* 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; +} + +/** + * Converts a wide string to UTF-8. + * + * @param dest The buffer to receive the UTF-8 string. + * @param dest_size The size of the buffer, in bytes. + * @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 + */ int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src) { - return WideCharToMultiByte(CP_UTF8, 0, src, -1, dest, (int)dest_size, NULL, NULL); + /* 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; +} + +/** + * Converts a UTF-8 string to wide characters. + * Memory is allocated to hold the converted string. + * The caller is responsible for freeing the string with git__free. + * + * @param dest Receives a pointer to 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 + */ +int git__utf8_to_16_alloc(wchar_t **dest, const char *src) +{ + int utf16_size; + + *dest = NULL; + + /* 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) + return -1; + + *dest = git__malloc(utf16_size * sizeof(wchar_t)); + + if (!*dest) + return -1; + + utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, *dest, utf16_size); + + if (!utf16_size) { + /* Don't let git__free stomp on the thread-local last error code, + * so that the caller can call giterr_set(GITERR_OS, ...) */ + DWORD last_error = GetLastError(); + git__free(*dest); + *dest = NULL; + SetLastError(last_error); + } + + /* 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 utf16_size - 1; +} + +/** + * Converts a wide string to UTF-8. + * Memory is allocated to hold the converted string. + * The caller is responsible for freeing the string with git__free. + * + * @param dest Receives a pointer to 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 + */ +int git__utf16_to_8_alloc(char **dest, const wchar_t *src) +{ + int utf8_size; + DWORD dwFlags = get_wc_flags(); + + *dest = NULL; + + /* 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) + return -1; + + *dest = git__malloc(utf8_size); + + if (!*dest) + return -1; + + utf8_size = WideCharToMultiByte(CP_UTF8, dwFlags, src, -1, *dest, utf8_size, NULL, NULL); + + if (!utf8_size) { + /* Don't let git__free stomp on the thread-local last error code, + * so that the caller can call giterr_set(GITERR_OS, ...) */ + DWORD last_error = GetLastError(); + git__free(*dest); + *dest = NULL; + SetLastError(last_error); + } + + /* 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 utf8_size - 1; } diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h index 3af77580e..a480cd93e 100644 --- a/src/win32/utf-conv.h +++ b/src/win32/utf-conv.h @@ -10,27 +10,83 @@ #include <wchar.h> #include "common.h" -/* Maximum characters in a Windows path plus one for NUL byte */ -#define GIT_WIN_PATH_UTF16 (260 + 1) +/* 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 bytes necessary to convert a full-length UTF16 path to UTF8 */ -#define GIT_WIN_PATH_UTF8 (260 * 4 + 1) +/* 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]; -typedef char git_win32_path_as_utf8[GIT_WIN_PATH_UTF8]; +/** + * Converts a UTF-8 string to wide characters. + * + * @param dest The buffer to receive the wide string. + * @param dest_size The size of the buffer, in characters. + * @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 + */ +int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src); -/* dest_size is the size of dest in wchar_t's */ -int git__utf8_to_16(wchar_t * dest, size_t dest_size, const char *src); -/* dest_size is the size of dest in char's */ +/** + * Converts a wide string to UTF-8. + * + * @param dest The buffer to receive the UTF-8 string. + * @param dest_size The size of the buffer, in bytes. + * @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 + */ int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src); -GIT_INLINE(int) git_win32_path_from_c(git_win32_path dest, const char *src) +/** + * Converts a UTF-8 string to wide characters. + * Memory is allocated to hold the converted string. + * The caller is responsible for freeing the string with git__free. + * + * @param dest Receives a pointer to 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 + */ +int git__utf8_to_16_alloc(wchar_t **dest, const char *src); + +/** + * Converts a wide string to UTF-8. + * Memory is allocated to hold the converted string. + * The caller is responsible for freeing the string with git__free. + * + * @param dest Receives a pointer to 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 + */ +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); } -GIT_INLINE(int) git_win32_path_to_c(git_win32_path_as_utf8 dest, const wchar_t *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); } diff --git a/src/win32/w32_util.c b/src/win32/w32_util.c new file mode 100644 index 000000000..246b30a6b --- /dev/null +++ b/src/win32/w32_util.c @@ -0,0 +1,69 @@ +/* + * 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 "w32_util.h" + +/** + * Creates a FindFirstFile(Ex) filter string from a UTF-8 path. + * The filter string enumerates all items in the directory. + * + * @param dest The buffer to receive the filter string. + * @param src The UTF-8 path of the directory to enumerate. + * @return True if the filter string was created successfully; false otherwise + */ +bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src) +{ + static const wchar_t suffix[] = L"\\*"; + int len = git_win32_path_from_utf8(dest, src); + + /* Ensure the path was converted */ + if (len < 0) + return false; + + /* Ensure that the path does not end with a trailing slash -- + * because we're about to add one! */ + if (len > 0 && + (dest[len - 1] == L'/' || dest[len - 1] == L'\\')) { + dest[len - 1] = L'\0'; + len--; + } + + /* Ensure we have enough room to add the suffix */ + if ((size_t)len > GIT_WIN_PATH_UTF16 - ARRAY_SIZE(suffix)) + return false; + + wcscat(dest, suffix); + return true; +} + +/** + * Ensures the given path (file or folder) has the +H (hidden) attribute set. + * + * @param path The path which should receive the +H bit. + * @return 0 on success; -1 on failure + */ +int git_win32__sethidden(const char *path) +{ + git_win32_path buf; + DWORD attrs; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + attrs = GetFileAttributesW(buf); + + /* Ensure the path exists */ + if (INVALID_FILE_ATTRIBUTES == attrs) + return -1; + + /* If the item isn't already +H, add the bit */ + if (0 == (attrs & FILE_ATTRIBUTE_HIDDEN) && + !SetFileAttributesW(buf, attrs | FILE_ATTRIBUTE_HIDDEN)) + return -1; + + return 0; +} diff --git a/src/win32/w32_util.h b/src/win32/w32_util.h new file mode 100644 index 000000000..cd02bd7b2 --- /dev/null +++ b/src/win32/w32_util.h @@ -0,0 +1,31 @@ +/* + * 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_w32_util_h__ +#define INCLUDE_w32_util_h__ + +#include "utf-conv.h" + +/** + * Creates a FindFirstFile(Ex) filter string from a UTF-8 path. + * The filter string enumerates all items in the directory. + * + * @param dest The buffer to receive the filter string. + * @param src The UTF-8 path of the directory to enumerate. + * @return True if the filter string was created successfully; false otherwise + */ +bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src); + +/** + * Ensures the given path (file or folder) has the +H (hidden) attribute set. + * + * @param path The path which should receive the +H bit. + * @return 0 on success; -1 on failure + */ +int git_win32__sethidden(const char *path); + +#endif diff --git a/tests/clar_libgit2.c b/tests/clar_libgit2.c index 90e53c5e6..6f6143dad 100644 --- a/tests/clar_libgit2.c +++ b/tests/clar_libgit2.c @@ -59,47 +59,40 @@ void cl_git_rewritefile(const char *path, const char *content) char *cl_getenv(const char *name) { - git_win32_path name_utf16; - DWORD alloc_len; - wchar_t *value_utf16; - char *value_utf8; + wchar_t *wide_name, *wide_value; + char *utf8_value = NULL; + DWORD value_len; - git_win32_path_from_c(name_utf16, name); - alloc_len = GetEnvironmentVariableW(name_utf16, NULL, 0); - if (alloc_len <= 0) - return NULL; + cl_assert(git__utf8_to_16_alloc(&wide_name, name) >= 0); - cl_assert(value_utf16 = git__calloc(alloc_len, sizeof(wchar_t))); + value_len = GetEnvironmentVariableW(wide_name, NULL, 0); - GetEnvironmentVariableW(name_utf16, value_utf16, alloc_len); - - alloc_len = alloc_len * 4 + 1; /* worst case UTF16->UTF8 growth */ - cl_assert(value_utf8 = git__calloc(alloc_len, 1)); - - git__utf16_to_8(value_utf8, alloc_len, value_utf16); - - git__free(value_utf16); + if (value_len) { + cl_assert(wide_value = git__malloc(value_len * sizeof(wchar_t))); + cl_assert(GetEnvironmentVariableW(wide_name, wide_value, value_len)); + cl_assert(git__utf16_to_8_alloc(&utf8_value, wide_value) >= 0); + git__free(wide_value); + } - return value_utf8; + git__free(wide_name); + return utf8_value; } int cl_setenv(const char *name, const char *value) { - git_win32_path name_utf16; - git_win32_path value_utf16; + wchar_t *wide_name, *wide_value; - git_win32_path_from_c(name_utf16, name); + cl_assert(git__utf8_to_16_alloc(&wide_name, name) >= 0); if (value) { - git_win32_path_from_c(value_utf16, value); - cl_assert(SetEnvironmentVariableW(name_utf16, value_utf16)); + cl_assert(git__utf8_to_16_alloc(&wide_value, value) >= 0); + cl_assert(SetEnvironmentVariableW(wide_name, wide_value)); } else { /* Windows XP returns 0 (failed) when passing NULL for lpValue when - * lpName does not exist in the environment block. This behavior - * seems to have changed in later versions. Don't check return value - * of SetEnvironmentVariable when passing NULL for lpValue. - */ - SetEnvironmentVariableW(name_utf16, NULL); + * lpName does not exist in the environment block. This behavior + * seems to have changed in later versions. Don't check the return value + * of SetEnvironmentVariable when passing NULL for lpValue. */ + SetEnvironmentVariableW(wide_name, NULL); } return 0; @@ -115,8 +108,8 @@ int cl_rename(const char *source, const char *dest) git_win32_path dest_utf16; unsigned retries = 1; - git_win32_path_from_c(source_utf16, source); - git_win32_path_from_c(dest_utf16, dest); + cl_assert(git_win32_path_from_utf8(source_utf16, source) >= 0); + cl_assert(git_win32_path_from_utf8(dest_utf16, dest) >= 0); while (!MoveFileW(source_utf16, dest_utf16)) { /* Only retry if the error is ERROR_ACCESS_DENIED; diff --git a/tests/core/env.c b/tests/core/env.c index b01ad1c24..df1d92a02 100644 --- a/tests/core/env.c +++ b/tests/core/env.c @@ -21,7 +21,7 @@ static char *home_values[] = { "f\xc4\x80ke_\xc4\xa4ome", /* latin extended */ "f\xce\xb1\xce\xba\xce\xb5_h\xce\xbfm\xce\xad", /* having fun with greek */ "fa\xe0" "\xb8" "\x87" "e_\xe0" "\xb8" "\x99" "ome", /* thai characters */ - "f\xe1\x9cx80ke_\xe1\x9c\x91ome", /* tagalog characters */ + "f\xe1\x9c\x80ke_\xe1\x9c\x91ome", /* tagalog characters */ "\xe1\xb8\x9f\xe1\xba\xa2" "ke_ho" "\xe1" "\xb9" "\x81" "e", /* latin extended additional */ "\xf0\x9f\x98\x98\xf0\x9f\x98\x82", /* emoticons */ NULL |