Commit 5ca410b9 by Vicent Marti

Merge pull request #2283 from phkelley/win32_fs

Win32: UTF-8 <-> WCHAR conversion overhaul
parents 5b58d6f7 7110000d
......@@ -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 (hFind != INVALID_HANDLE_VALUE) {
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
......
......@@ -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;
}
......
......@@ -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,
......
......@@ -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 */
......
......@@ -22,6 +22,15 @@
#define GIT_DATE_RFC2822_SZ 32
/**
* Return the length of a constant string.
* We are aware that `strlen` performs the same task and is usually
* optimized away by the compiler, whilst being safer because it returns
* valid values when passed a pointer instead of a constant string; however
* this macro will transparently work with wide-char and single-char strings.
*/
#define CONST_STRLEN(x) ((sizeof(x)/sizeof(x[0])) - 1)
/*
* Custom memory allocation wrappers
* that set error code and error message
......
......@@ -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)
......
......@@ -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 {
......
......@@ -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);
}
......
......@@ -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) &&
dwType == REG_SZ) {
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);
......
......@@ -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);
......
......@@ -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 */
......
......@@ -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_readlink(const char *path, char *buf, size_t bufsiz);
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);
......
/*
* 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_win32_reparse_h__
#define INCLUDE_git_win32_reparse_h__
/* This structure is defined on MSDN at
* http://msdn.microsoft.com/en-us/library/windows/hardware/ff552012(v=vs.85).aspx
*
* It was formerly included in the Windows 2000 SDK and remains defined in
* MinGW, so we must define it with a silly name to avoid conflicting.
*/
typedef struct _GIT_REPARSE_DATA_BUFFER {
ULONG ReparseTag;
USHORT ReparseDataLength;
USHORT Reserved;
union {
struct {
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
ULONG Flags;
WCHAR PathBuffer[1];
} SymbolicLinkReparseBuffer;
struct {
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
WCHAR PathBuffer[1];
} MountPointReparseBuffer;
struct {
UCHAR DataBuffer[1];
} GenericReparseBuffer;
};
} GIT_REPARSE_DATA_BUFFER;
#define REPARSE_DATA_HEADER_SIZE 8
#define REPARSE_DATA_MOUNTPOINT_HEADER_SIZE 8
#define REPARSE_DATA_UNION_SIZE 12
/* Missing in MinGW */
#ifndef FSCTL_GET_REPARSE_POINT
# define FSCTL_GET_REPARSE_POINT 0x000900a8
#endif
/* Missing in MinGW */
#ifndef FSCTL_SET_REPARSE_POINT
# define FSCTL_SET_REPARSE_POINT 0x000900a4
#endif
#endif
......@@ -8,12 +8,131 @@
#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) {
git__free(*dest);
*dest = NULL;
}
/* 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) {
git__free(*dest);
*dest = NULL;
}
/* 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;
}
......@@ -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);
}
......
/*
* 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. Don't rely our trim_end
* helper, because we want to remove the backslash even for
* drive letter paths, in this case. */
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 - CONST_STRLEN(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 (attrs == INVALID_FILE_ATTRIBUTES)
return -1;
/* If the item isn't already +H, add the bit */
if ((attrs & FILE_ATTRIBUTE_HIDDEN) == 0 &&
!SetFileAttributesW(buf, attrs | FILE_ATTRIBUTE_HIDDEN))
return -1;
return 0;
}
/**
* Removes any trailing backslashes from a path, except in the case of a drive
* letter path (C:\, D:\, etc.). This function cannot fail.
*
* @param path The path which should be trimmed.
* @return The length of the modified string (<= the input length)
*/
size_t git_win32__path_trim_end(wchar_t *str, size_t len)
{
while (1) {
if (!len || str[len - 1] != L'\\')
break;
/* Don't trim backslashes from drive letter paths, which
* are 3 characters long and of the form C:\, D:\, etc. */
if (len == 3 && git_win32__isalpha(str[0]) && str[1] == ':')
break;
len--;
}
str[len] = L'\0';
return len;
}
/**
* Removes any of the following namespace prefixes from a path,
* if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail.
*
* @param path The path which should be converted.
* @return The length of the modified string (<= the input length)
*/
size_t git_win32__canonicalize_path(wchar_t *str, size_t len)
{
static const wchar_t dosdevices_prefix[] = L"\\\?\?\\";
static const wchar_t nt_prefix[] = L"\\\\?\\";
static const wchar_t unc_prefix[] = L"UNC\\";
size_t to_advance = 0;
/* "\??\" -- DOS Devices prefix */
if (len >= CONST_STRLEN(dosdevices_prefix) &&
!wcsncmp(str, dosdevices_prefix, CONST_STRLEN(dosdevices_prefix))) {
to_advance += CONST_STRLEN(dosdevices_prefix);
len -= CONST_STRLEN(dosdevices_prefix);
}
/* "\\?\" -- NT namespace prefix */
else if (len >= CONST_STRLEN(nt_prefix) &&
!wcsncmp(str, nt_prefix, CONST_STRLEN(nt_prefix))) {
to_advance += CONST_STRLEN(nt_prefix);
len -= CONST_STRLEN(nt_prefix);
}
/* "\??\UNC\", "\\?\UNC\" -- UNC prefix */
if (to_advance && len >= CONST_STRLEN(unc_prefix) &&
!wcsncmp(str + to_advance, unc_prefix, CONST_STRLEN(unc_prefix))) {
to_advance += CONST_STRLEN(unc_prefix);
len -= CONST_STRLEN(unc_prefix);
}
if (to_advance) {
memmove(str, str + to_advance, len * sizeof(wchar_t));
str[len] = L'\0';
}
return git_win32__path_trim_end(str, len);
}
/*
* 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"
GIT_INLINE(bool) git_win32__isalpha(wchar_t c)
{
return ((c >= L'A' && c <= L'Z') || (c >= L'a' && c <= L'z'));
}
/**
* 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);
/**
* Removes any trailing backslashes from a path, except in the case of a drive
* letter path (C:\, D:\, etc.). This function cannot fail.
*
* @param path The path which should be trimmed.
* @return The length of the modified string (<= the input length)
*/
size_t git_win32__path_trim_end(wchar_t *str, size_t len);
/**
* Removes any of the following namespace prefixes from a path,
* if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail.
*
* @param path The path which should be converted.
* @return The length of the modified string (<= the input length)
*/
size_t git_win32__canonicalize_path(wchar_t *str, size_t len);
#endif
......@@ -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;
......
......@@ -29,6 +29,17 @@
#define cl_git_fail_with(expr, error) cl_assert_equal_i(error,expr)
/**
* Like cl_git_pass, only for Win32 error code conventions
*/
#define cl_win32_pass(expr) do { \
int _win32_res; \
if ((_win32_res = (expr)) == 0) { \
giterr_set(GITERR_OS, "Returned: %d, system error code: %d", _win32_res, GetLastError()); \
cl_git_report_failure(_win32_res, __FILE__, __LINE__, "System call failed: " #expr); \
} \
} while(0)
void cl_git_report_failure(int, const char *, int, const char *);
#define cl_assert_at_line(expr,file,line) \
......
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment