* 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 "posix.h"
#include "repository.h"
#ifdef GIT_WIN32
#include "win32/w32_buffer.h"
#include "win32/w32_util.h"
#include "win32/version.h"
+#include <aclapi.h>
#else
#include <dirent.h>
#endif
#include <stdio.h>
#include <ctype.h>
-#define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':')
+static int dos_drive_prefix_length(const char *path)
+{
+ int i;
+
+ /*
+ * Does it start with an ASCII letter (i.e. highest bit not set),
+ * followed by a colon?
+ */
+ if (!(0x80 & (unsigned char)*path))
+ return *path && path[1] == ':' ? 2 : 0;
+
+ /*
+ * While drive letters must be letters of the English alphabet, it is
+ * possible to assign virtually _any_ Unicode character via `subst` as
+ * a drive letter to "virtual drives". Even `1`, or `ä`. Or fun stuff
+ * like this:
+ *
+ * subst ֍: %USERPROFILE%\Desktop
+ */
+ for (i = 1; i < 4 && (0x80 & (unsigned char)path[i]); i++)
+ ; /* skip first UTF-8 character */
+ return path[i] == ':' ? i + 1 : 0;
+}
#ifdef GIT_WIN32
static bool looks_like_network_computer_name(const char *path, int pos)
return result;
}
+/*
+ * Determine if the path is a Windows prefix and, if so, returns
+ * its actual lentgh. If it is not a prefix, returns -1.
+ */
+static int win32_prefix_length(const char *path, int len)
+{
+#ifndef GIT_WIN32
+ GIT_UNUSED(path);
+ GIT_UNUSED(len);
+#else
+ /*
+ * Mimic unix behavior where '/.git' returns '/': 'C:/.git'
+ * will return 'C:/' here
+ */
+ if (dos_drive_prefix_length(path) == len)
+ return len;
+
+ /*
+ * Similarly checks if we're dealing with a network computer name
+ * '//computername/.git' will return '//computername/'
+ */
+ if (looks_like_network_computer_name(path, len))
+ return len;
+#endif
+
+ return -1;
+}
+
/*
* Based on the Android implementation, BSD licensed.
* Check http://android.git.kernel.org/
int git_path_dirname_r(git_buf *buffer, const char *path)
{
const char *endp;
- int result, len;
+ int is_prefix = 0, len;
/* Empty or NULL string gets treated as "." */
if (path == NULL || *path == '\0') {
while (endp > path && *endp == '/')
endp--;
+ if (endp - path + 1 > INT_MAX) {
+ git_error_set(GIT_ERROR_INVALID, "path too long");
+ len = -1;
+ goto Exit;
+ }
+
+ if ((len = win32_prefix_length(path, (int)(endp - path + 1))) > 0) {
+ is_prefix = 1;
+ goto Exit;
+ }
+
/* Find the start of the dir */
while (endp > path && *endp != '/')
endp--;
endp--;
} while (endp > path && *endp == '/');
- /* Cast is safe because max path < max int */
- len = (int)(endp - path + 1);
-
-#ifdef GIT_WIN32
- /* Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return
- 'C:/' here */
-
- if (len == 2 && LOOKS_LIKE_DRIVE_PREFIX(path)) {
- len = 3;
+ if (endp - path + 1 > INT_MAX) {
+ git_error_set(GIT_ERROR_INVALID, "path too long");
+ len = -1;
goto Exit;
}
- /* Similarly checks if we're dealing with a network computer name
- '//computername/.git' will return '//computername/' */
-
- if (looks_like_network_computer_name(path, len)) {
- len++;
+ if ((len = win32_prefix_length(path, (int)(endp - path + 1))) > 0) {
+ is_prefix = 1;
goto Exit;
}
-#endif
+ /* Cast is safe because max path < max int */
+ len = (int)(endp - path + 1);
Exit:
- result = len;
-
- if (buffer != NULL && git_buf_set(buffer, path, len) < 0)
- return -1;
+ if (buffer) {
+ if (git_buf_set(buffer, path, len) < 0)
+ return -1;
+ if (is_prefix && git_buf_putc(buffer, '/') < 0)
+ return -1;
+ }
- return result;
+ return len;
}
git_path_dirname_r(&buf, path);
dirname = git_buf_detach(&buf);
- git_buf_free(&buf); /* avoid memleak if error occurs */
+ git_buf_dispose(&buf); /* avoid memleak if error occurs */
return dirname;
}
git_path_basename_r(&buf, path);
basename = git_buf_detach(&buf);
- git_buf_free(&buf); /* avoid memleak if error occurs */
+ git_buf_dispose(&buf); /* avoid memleak if error occurs */
return basename;
}
return 0;
}
-const char *git_path_topdir(const char *path)
-{
- size_t len;
- ssize_t i;
-
- assert(path);
- len = strlen(path);
-
- if (!len || path[len - 1] != '/')
- return NULL;
-
- for (i = (ssize_t)len - 2; i >= 0; --i)
- if (path[i] == '/')
- break;
-
- return &path[i + 1];
-}
-
int git_path_root(const char *path)
{
- int offset = 0;
+ int offset = 0, prefix_len;
/* Does the root of the path look like a windows drive ? */
- if (LOOKS_LIKE_DRIVE_PREFIX(path))
- offset += 2;
+ if ((prefix_len = dos_drive_prefix_length(path)))
+ offset += prefix_len;
#ifdef GIT_WIN32
/* Are we dealing with a windows network path? */
while (path[offset] && path[offset] != '/' && path[offset] != '\\')
offset++;
}
+
+ if (path[offset] == '\\')
+ return offset;
#endif
- if (path[offset] == '/' || path[offset] == '\\')
+ if (path[offset] == '/')
return offset;
return -1; /* Not a real error - signals that path is not rooted */
}
-void git_path_trim_slashes(git_buf *path)
+static void path_trim_slashes(git_buf *path)
{
int ceiling = git_path_root(path->ptr) + 1;
- assert(ceiling >= 0);
+
+ if (ceiling < 0)
+ return;
while (path->size > (size_t)ceiling) {
if (path->ptr[path->size-1] != '/')
{
ssize_t root;
- assert(path && path_out);
+ GIT_ASSERT_ARG(path_out);
+ GIT_ASSERT_ARG(path);
root = (ssize_t)git_path_root(path);
return 0;
}
+void git_path_squash_slashes(git_buf *path)
+{
+ char *p, *q;
+
+ if (path->size == 0)
+ return;
+
+ for (p = path->ptr, q = path->ptr; *q; p++, q++) {
+ *p = *q;
+
+ while (*q == '/' && *(q+1) == '/') {
+ path->size--;
+ q++;
+ }
+ }
+
+ *p = '\0';
+}
+
int git_path_prettify(git_buf *path_out, const char *path, const char *base)
{
char buf[GIT_PATH_MAX];
- assert(path && path_out);
+ GIT_ASSERT_ARG(path_out);
+ GIT_ASSERT_ARG(path);
/* construct path if needed */
if (base != NULL && git_path_root(path) < 0) {
}
if (p_realpath(path, buf) == NULL) {
- /* giterr_set resets the errno when dealing with a GITERR_OS kind of error */
+ /* git_error_set resets the errno when dealing with a GIT_ERROR_OS kind of error */
int error = (errno == ENOENT || errno == ENOTDIR) ? GIT_ENOTFOUND : -1;
- giterr_set(GITERR_OS, "Failed to resolve path '%s'", path);
+ git_error_set(GIT_ERROR_OS, "failed to resolve path '%s'", path);
git_buf_clear(path_out);
return git_buf_oom(path) ? -1 : 0;
}
-void git_path_string_to_dir(char* path, size_t size)
+void git_path_string_to_dir(char *path, size_t size)
{
size_t end = strlen(path);
int git__percent_decode(git_buf *decoded_out, const char *input)
{
int len, hi, lo, i;
- assert(decoded_out && input);
+
+ GIT_ASSERT_ARG(decoded_out);
+ GIT_ASSERT_ARG(input);
len = (int)strlen(input);
git_buf_clear(decoded_out);
static int error_invalid_local_file_uri(const char *uri)
{
- giterr_set(GITERR_CONFIG, "'%s' is not a valid local file URI", uri);
+ git_error_set(GIT_ERROR_CONFIG, "'%s' is not a valid local file URI", uri);
return -1;
}
{
int offset;
- assert(local_path_out && file_url);
+ GIT_ASSERT_ARG(local_path_out);
+ GIT_ASSERT_ARG(file_url);
if ((offset = local_file_url_prefixlen(file_url)) < 0 ||
file_url[offset] == '\0' || file_url[offset] == '/')
ssize_t stop = 0, scan;
char oldc = '\0';
- assert(path && cb);
+ GIT_ASSERT_ARG(path);
+ GIT_ASSERT_ARG(cb);
if (ceiling != NULL) {
if (git__prefixcmp(path->ptr, ceiling) == 0)
if (!scan) {
error = cb(data, "");
if (error)
- giterr_set_after_callback(error);
+ git_error_set_after_callback(error);
return error;
}
iter.ptr[scan] = oldc;
if (error) {
- giterr_set_after_callback(error);
+ git_error_set_after_callback(error);
break;
}
if (!error && stop == 0 && iter.ptr[0] != '/') {
error = cb(data, "");
if (error)
- giterr_set_after_callback(error);
+ git_error_set_after_callback(error);
}
return error;
bool git_path_exists(const char *path)
{
- assert(path);
+ GIT_ASSERT_ARG_WITH_RETVAL(path, false);
return p_access(path, F_OK) == 0;
}
{
struct stat st;
- assert(path);
+ GIT_ASSERT_ARG_WITH_RETVAL(path, false);
if (p_stat(path, &st) < 0)
return false;
return S_ISREG(st.st_mode) != 0;
}
+bool git_path_islink(const char *path)
+{
+ struct stat st;
+
+ GIT_ASSERT_ARG_WITH_RETVAL(path, false);
+ if (p_lstat(path, &st) < 0)
+ return false;
+
+ return S_ISLNK(st.st_mode) != 0;
+}
+
#ifdef GIT_WIN32
bool git_path_is_empty_dir(const char *path)
return false;
if ((error = git_buf_sets(&dir, path)) != 0)
- giterr_clear();
+ git_error_clear();
else
error = git_path_direach(&dir, 0, path_found_entry, NULL);
- git_buf_free(&dir);
+ git_buf_dispose(&dir);
return !error;
}
switch (errno_value) {
case ENOENT:
case ENOTDIR:
- giterr_set(GITERR_OS, "Could not find '%s' to %s", path, action);
+ git_error_set(GIT_ERROR_OS, "could not find '%s' to %s", path, action);
return GIT_ENOTFOUND;
case EINVAL:
case ENAMETOOLONG:
- giterr_set(GITERR_OS, "Invalid path for filesystem '%s'", path);
+ git_error_set(GIT_ERROR_OS, "invalid path for filesystem '%s'", path);
return GIT_EINVALIDSPEC;
case EEXIST:
- giterr_set(GITERR_OS, "Failed %s - '%s' already exists", action, path);
+ git_error_set(GIT_ERROR_OS, "failed %s - '%s' already exists", action, path);
return GIT_EEXISTS;
+ case EACCES:
+ git_error_set(GIT_ERROR_OS, "failed %s - '%s' is locked", action, path);
+ return GIT_ELOCKED;
+
default:
- giterr_set(GITERR_OS, "Could not %s '%s'", action, path);
+ git_error_set(GIT_ERROR_OS, "could not %s '%s'", action, path);
return -1;
}
}
return false;
/* save excursion */
- git_buf_joinpath(dir, dir->ptr, sub);
+ if (git_buf_joinpath(dir, dir->ptr, sub) < 0)
+ return false;
result = predicate(dir->ptr);
return _check_dir_contents(base, file, &git_path_isfile);
}
-int git_path_find_dir(git_buf *dir, const char *path, const char *base)
+int git_path_find_dir(git_buf *dir)
{
- int error = git_path_join_unrooted(dir, path, base, NULL);
+ int error = 0;
+ char buf[GIT_PATH_MAX];
- if (!error) {
- char buf[GIT_PATH_MAX];
- if (p_realpath(dir->ptr, buf) != NULL)
- error = git_buf_sets(dir, buf);
- }
+ if (p_realpath(dir->ptr, buf) != NULL)
+ error = git_buf_sets(dir, buf);
/* call dirname if this is not a directory */
if (!error) /* && git_path_isdir(dir->ptr) == false) */
char *base, *to, *from, *next;
size_t len;
- if (!path || git_buf_oom(path))
- return -1;
+ GIT_ERROR_CHECK_ALLOC_BUF(path);
if (ceiling > path->size)
ceiling = path->size;
else if (len == 2 && from[0] == '.' && from[1] == '.') {
/* error out if trying to up one from a hard base */
if (to == base && ceiling != 0) {
- giterr_set(GITERR_INVALID,
- "Cannot strip root component off url");
+ git_error_set(GIT_ERROR_INVALID,
+ "cannot strip root component off url");
return -1;
}
int git_path_apply_relative(git_buf *target, const char *relpath)
{
- git_buf_joinpath(target, git_buf_cstr(target), relpath);
- return git_path_resolve_relative(target, 0);
+ return git_buf_joinpath(target, git_buf_cstr(target), relpath) ||
+ git_path_resolve_relative(target, 0);
}
int git_path_cmp(
return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
}
+size_t git_path_common_dirlen(const char *one, const char *two)
+{
+ const char *p, *q, *dirsep = NULL;
+
+ for (p = one, q = two; *p && *q; p++, q++) {
+ if (*p == '/' && *q == '/')
+ dirsep = p;
+ else if (*p != *q)
+ break;
+ }
+
+ return dirsep ? (dirsep - one) + 1 : 0;
+}
+
int git_path_make_relative(git_buf *path, const char *parent)
{
const char *p, *q, *p_dirsep, *q_dirsep;
/* need at least 1 common path segment */
if ((p_dirsep == path->ptr || q_dirsep == parent) &&
(*p_dirsep != '/' || *q_dirsep != '/')) {
- giterr_set(GITERR_INVALID,
+ git_error_set(GIT_ERROR_INVALID,
"%s is not a parent of %s", parent, path->ptr);
return GIT_ENOTFOUND;
}
for (; (q = strchr(q, '/')) && *(q + 1); q++)
depth++;
- GITERR_CHECK_ALLOC_MULTIPLY(&newlen, depth, 3);
- GITERR_CHECK_ALLOC_ADD(&newlen, newlen, plen);
+ GIT_ERROR_CHECK_ALLOC_MULTIPLY(&newlen, depth, 3);
+ GIT_ERROR_CHECK_ALLOC_ADD(&newlen, newlen, plen);
- GITERR_CHECK_ALLOC_ADD(&alloclen, newlen, 1);
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, newlen, 1);
/* save the offset as we might realllocate the pointer */
offset = p - path->ptr;
if (ic) {
if (ic->map != (iconv_t)-1)
iconv_close(ic->map);
- git_buf_free(&ic->buf);
+ git_buf_dispose(&ic->buf);
}
}
git_buf_clear(&ic->buf);
while (1) {
- GITERR_CHECK_ALLOC_ADD(&alloclen, wantlen, 1);
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, wantlen, 1);
if (git_buf_grow(&ic->buf, alloclen) < 0)
return -1;
return 0;
fail:
- giterr_set(GITERR_OS, "Unable to convert unicode path data");
+ git_error_set(GIT_ERROR_OS, "unable to convert unicode path data");
return -1;
}
(void)p_unlink(path.ptr);
done:
- git_buf_free(&path);
+ git_buf_dispose(&path);
return found_decomposed;
}
wd_len = git_buf_len(path);
if ((dir = opendir(path->ptr)) == NULL) {
- giterr_set(GITERR_OS, "Failed to open directory '%s'", path->ptr);
+ git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path->ptr);
if (errno == ENOENT)
return GIT_ENOTFOUND;
if ((error = git_buf_put(path, de_path, de_len)) < 0)
break;
- giterr_clear();
+ git_error_clear();
error = fn(arg, path);
git_buf_truncate(path, wd_len); /* restore path */
/* Only set our own error if the callback did not set one already */
if (error != 0) {
- if (!giterr_last())
- giterr_set_after_callback(error);
+ if (!git_error_last())
+ git_error_set_after_callback(error);
break;
}
unsigned int flags)
{
git_win32_path path_filter;
- git_buf hack = {0};
static int is_win7_or_later = -1;
if (is_win7_or_later < 0)
is_win7_or_later = git_has_win32_version(6, 1, 0);
- assert(diriter && path);
+ GIT_ASSERT_ARG(diriter);
+ GIT_ASSERT_ARG(path);
memset(diriter, 0, sizeof(git_path_diriter));
diriter->handle = INVALID_HANDLE_VALUE;
if (git_buf_puts(&diriter->path_utf8, path) < 0)
return -1;
- git_path_trim_slashes(&diriter->path_utf8);
+ path_trim_slashes(&diriter->path_utf8);
if (diriter->path_utf8.size == 0) {
- giterr_set(GITERR_FILESYSTEM, "Could not open directory '%s'", path);
+ git_error_set(GIT_ERROR_FILESYSTEM, "could not open directory '%s'", path);
return -1;
}
if ((diriter->parent_len = git_win32_path_from_utf8(diriter->path, diriter->path_utf8.ptr)) < 0 ||
!git_win32__findfirstfile_filter(path_filter, diriter->path_utf8.ptr)) {
- giterr_set(GITERR_OS, "Could not parse the directory path '%s'", path);
+ git_error_set(GIT_ERROR_OS, "could not parse the directory path '%s'", path);
return -1;
}
is_win7_or_later ? FIND_FIRST_EX_LARGE_FETCH : 0);
if (diriter->handle == INVALID_HANDLE_VALUE) {
- giterr_set(GITERR_OS, "Could not open directory '%s'", path);
+ git_error_set(GIT_ERROR_OS, "could not open directory '%s'", path);
return -1;
}
return -1;
if (path_len > GIT_WIN_PATH_UTF16) {
- giterr_set(GITERR_FILESYSTEM,
+ git_error_set(GIT_ERROR_FILESYSTEM,
"invalid path '%.*ls\\%ls' (path too long)",
diriter->parent_len, diriter->path, diriter->current.cFileName);
return -1;
size_t *out_len,
git_path_diriter *diriter)
{
- assert(out && out_len && diriter);
-
- assert(diriter->path_utf8.size > diriter->parent_utf8_len);
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(out_len);
+ GIT_ASSERT_ARG(diriter);
+ GIT_ASSERT(diriter->path_utf8.size > diriter->parent_utf8_len);
*out = &diriter->path_utf8.ptr[diriter->parent_utf8_len+1];
*out_len = diriter->path_utf8.size - diriter->parent_utf8_len - 1;
size_t *out_len,
git_path_diriter *diriter)
{
- assert(out && out_len && diriter);
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(out_len);
+ GIT_ASSERT_ARG(diriter);
*out = diriter->path_utf8.ptr;
*out_len = diriter->path_utf8.size;
int git_path_diriter_stat(struct stat *out, git_path_diriter *diriter)
{
- assert(out && diriter);
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(diriter);
return git_win32__file_attribute_to_stat(out,
(WIN32_FILE_ATTRIBUTE_DATA *)&diriter->current,
if (diriter == NULL)
return;
- git_buf_free(&diriter->path_utf8);
+ git_buf_dispose(&diriter->path_utf8);
if (diriter->handle != INVALID_HANDLE_VALUE) {
FindClose(diriter->handle);
const char *path,
unsigned int flags)
{
- assert(diriter && path);
+ GIT_ASSERT_ARG(diriter);
+ GIT_ASSERT_ARG(path);
memset(diriter, 0, sizeof(git_path_diriter));
if (git_buf_puts(&diriter->path, path) < 0)
return -1;
- git_path_trim_slashes(&diriter->path);
+ path_trim_slashes(&diriter->path);
if (diriter->path.size == 0) {
- giterr_set(GITERR_FILESYSTEM, "Could not open directory '%s'", path);
+ git_error_set(GIT_ERROR_FILESYSTEM, "could not open directory '%s'", path);
return -1;
}
if ((diriter->dir = opendir(diriter->path.ptr)) == NULL) {
- git_buf_free(&diriter->path);
+ git_buf_dispose(&diriter->path);
- giterr_set(GITERR_OS, "Failed to open directory '%s'", path);
+ git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path);
return -1;
}
bool skip_dot = !(diriter->flags & GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT);
int error = 0;
- assert(diriter);
+ GIT_ASSERT_ARG(diriter);
errno = 0;
if (!errno)
return GIT_ITEROVER;
- giterr_set(GITERR_OS,
- "Could not read directory '%s'", diriter->path);
+ git_error_set(GIT_ERROR_OS,
+ "could not read directory '%s'", diriter->path.ptr);
return -1;
}
} while (skip_dot && git_path_is_dot_or_dotdot(de->d_name));
#endif
git_buf_truncate(&diriter->path, diriter->parent_len);
- git_buf_putc(&diriter->path, '/');
+
+ if (diriter->parent_len > 0 &&
+ diriter->path.ptr[diriter->parent_len-1] != '/')
+ git_buf_putc(&diriter->path, '/');
+
git_buf_put(&diriter->path, filename, filename_len);
if (git_buf_oom(&diriter->path))
size_t *out_len,
git_path_diriter *diriter)
{
- assert(out && out_len && diriter);
-
- assert(diriter->path.size > diriter->parent_len);
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(out_len);
+ GIT_ASSERT_ARG(diriter);
+ GIT_ASSERT(diriter->path.size > diriter->parent_len);
*out = &diriter->path.ptr[diriter->parent_len+1];
*out_len = diriter->path.size - diriter->parent_len - 1;
size_t *out_len,
git_path_diriter *diriter)
{
- assert(out && out_len && diriter);
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(out_len);
+ GIT_ASSERT_ARG(diriter);
*out = diriter->path.ptr;
*out_len = diriter->path.size;
int git_path_diriter_stat(struct stat *out, git_path_diriter *diriter)
{
- assert(out && diriter);
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(diriter);
return git_path_lstat(diriter->path.ptr, out);
}
git_path_iconv_clear(&diriter->ic);
#endif
- git_buf_free(&diriter->path);
+ git_buf_dispose(&diriter->path);
}
#endif
git_vector *contents,
const char *path,
size_t prefix_len,
- unsigned int flags)
+ uint32_t flags)
{
git_path_diriter iter = GIT_PATH_DIRITER_INIT;
const char *name;
char *dup;
int error;
- assert(contents && path);
+ GIT_ASSERT_ARG(contents);
+ GIT_ASSERT_ARG(path);
if ((error = git_path_diriter_init(&iter, path, flags)) < 0)
return error;
if ((error = git_path_diriter_fullpath(&name, &name_len, &iter)) < 0)
break;
- assert(name_len > prefix_len);
+ GIT_ASSERT(name_len > prefix_len);
dup = git__strndup(name + prefix_len, name_len - prefix_len);
- GITERR_CHECK_ALLOC(dup);
+ GIT_ERROR_CHECK_ALLOC(dup);
if ((error = git_vector_insert(contents, dup)) < 0)
break;
static int32_t next_hfs_char(const char **in, size_t *len)
{
while (*len) {
- int32_t codepoint;
- int cp_len = git__utf8_iterate((const uint8_t *)(*in), (int)(*len), &codepoint);
+ uint32_t codepoint;
+ int cp_len = git_utf8_iterate(&codepoint, *in, *len);
if (cp_len < 0)
return -1;
* the ASCII range, which is perfectly fine, because the
* git folder name can only be composed of ascii characters
*/
- return git__tolower(codepoint);
+ return git__tolower((int)codepoint);
}
return 0; /* NULL byte -- end of string */
}
-static bool verify_dotgit_hfs(const char *path, size_t len)
+static bool verify_dotgit_hfs_generic(const char *path, size_t len, const char *needle, size_t needle_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)
+ size_t i;
+ char c;
+
+ if (next_hfs_char(&path, &len) != '.')
+ return true;
+
+ for (i = 0; i < needle_len; i++) {
+ c = next_hfs_char(&path, &len);
+ if (c != needle[i])
+ return true;
+ }
+
+ if (next_hfs_char(&path, &len) != '\0')
return true;
return false;
}
+static bool verify_dotgit_hfs(const char *path, size_t len)
+{
+ return verify_dotgit_hfs_generic(path, len, "git", CONST_STRLEN("git"));
+}
+
GIT_INLINE(bool) verify_dotgit_ntfs(git_repository *repo, const char *path, size_t len)
{
git_buf *reserved = git_repository__reserved_names_win32;
if (!start)
return true;
- /* Reject paths like ".git\" */
- if (path[start] == '\\')
+ /*
+ * Reject paths that start with Windows-style directory separators
+ * (".git\") or NTFS alternate streams (".git:") and could be used
+ * to write to the ".git" directory on Windows platforms.
+ */
+ if (path[start] == '\\' || path[start] == ':')
return false;
/* Reject paths like '.git ' or '.git.' */
return false;
}
+/*
+ * Windows paths that end with spaces and/or dots are elided to the
+ * path without them for backward compatibility. That is to say
+ * that opening file "foo ", "foo." or even "foo . . ." will all
+ * map to a filename of "foo". This function identifies spaces and
+ * dots at the end of a filename, whether the proper end of the
+ * filename (end of string) or a colon (which would indicate a
+ * Windows alternate data stream.)
+ */
+GIT_INLINE(bool) ntfs_end_of_filename(const char *path)
+{
+ const char *c = path;
+
+ for (;; c++) {
+ if (*c == '\0' || *c == ':')
+ return true;
+ if (*c != ' ' && *c != '.')
+ return false;
+ }
+
+ return true;
+}
+
+GIT_INLINE(bool) verify_dotgit_ntfs_generic(const char *name, size_t len, const char *dotgit_name, size_t dotgit_len, const char *shortname_pfix)
+{
+ int i, saw_tilde;
+
+ if (name[0] == '.' && len >= dotgit_len &&
+ !strncasecmp(name + 1, dotgit_name, dotgit_len)) {
+ return !ntfs_end_of_filename(name + dotgit_len + 1);
+ }
+
+ /* Detect the basic NTFS shortname with the first six chars */
+ if (!strncasecmp(name, dotgit_name, 6) && name[6] == '~' &&
+ name[7] >= '1' && name[7] <= '4')
+ return !ntfs_end_of_filename(name + 8);
+
+ /* Catch fallback names */
+ for (i = 0, saw_tilde = 0; i < 8; i++) {
+ if (name[i] == '\0') {
+ return true;
+ } else if (saw_tilde) {
+ if (name[i] < '0' || name[i] > '9')
+ return true;
+ } else if (name[i] == '~') {
+ if (name[i+1] < '1' || name[i+1] > '9')
+ return true;
+ saw_tilde = 1;
+ } else if (i >= 6) {
+ return true;
+ } else if ((unsigned char)name[i] > 127) {
+ return true;
+ } else if (git__tolower(name[i]) != shortname_pfix[i]) {
+ return true;
+ }
+ }
+
+ return !ntfs_end_of_filename(name + i);
+}
+
GIT_INLINE(bool) verify_char(unsigned char c, unsigned int flags)
{
if ((flags & GIT_PATH_REJECT_BACKSLASH) && c == '\\')
return true;
}
+/*
+ * Return the length of the common prefix between str and prefix, comparing them
+ * case-insensitively (must be ASCII to match).
+ */
+GIT_INLINE(size_t) common_prefix_icase(const char *str, size_t len, const char *prefix)
+{
+ size_t count = 0;
+
+ while (len >0 && tolower(*str) == tolower(*prefix)) {
+ count++;
+ str++;
+ prefix++;
+ len--;
+ }
+
+ return count;
+}
+
/*
* 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
git_repository *repo,
const char *component,
size_t len,
+ uint16_t mode,
unsigned int flags)
{
if (len == 0)
return false;
}
- if (flags & GIT_PATH_REJECT_DOT_GIT_HFS &&
- !verify_dotgit_hfs(component, len))
- return false;
+ if (flags & GIT_PATH_REJECT_DOT_GIT_HFS) {
+ if (!verify_dotgit_hfs(component, len))
+ return false;
+ if (S_ISLNK(mode) && git_path_is_gitfile(component, len, GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_HFS))
+ 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_NTFS) {
+ if (!verify_dotgit_ntfs(repo, component, len))
+ return false;
+ if (S_ISLNK(mode) && git_path_is_gitfile(component, len, GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_NTFS))
+ return false;
+ }
+ /* don't bother rerunning the `.git` test if we ran the HFS or NTFS
+ * specific tests, they would have already rejected `.git`.
+ */
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;
+ (flags & GIT_PATH_REJECT_DOT_GIT_NTFS) == 0 &&
+ (flags & GIT_PATH_REJECT_DOT_GIT_LITERAL)) {
+ if (len >= 4 &&
+ component[0] == '.' &&
+ (component[1] == 'g' || component[1] == 'G') &&
+ (component[2] == 'i' || component[2] == 'I') &&
+ (component[3] == 't' || component[3] == 'T')) {
+ if (len == 4)
+ return false;
+
+ if (S_ISLNK(mode) && common_prefix_icase(component, len, ".gitmodules") == len)
+ return false;
+ }
+ }
return true;
}
git_repository *repo,
unsigned int flags)
{
- int protectHFS = 0, protectNTFS = 0;
+ int protectHFS = 0, protectNTFS = 1;
+ int error = 0;
+
+ flags |= GIT_PATH_REJECT_DOT_GIT_LITERAL;
#ifdef __APPLE__
protectHFS = 1;
#endif
-#ifdef GIT_WIN32
- protectNTFS = 1;
-#endif
-
if (repo && !protectHFS)
- git_repository__cvar(&protectHFS, repo, GIT_CVAR_PROTECTHFS);
- if (protectHFS)
+ error = git_repository__configmap_lookup(&protectHFS, repo, GIT_CONFIGMAP_PROTECTHFS);
+ if (!error && protectHFS)
flags |= GIT_PATH_REJECT_DOT_GIT_HFS;
- if (repo && !protectNTFS)
- git_repository__cvar(&protectNTFS, repo, GIT_CVAR_PROTECTNTFS);
- if (protectNTFS)
+ if (repo)
+ error = git_repository__configmap_lookup(&protectNTFS, repo, GIT_CONFIGMAP_PROTECTNTFS);
+ if (!error && protectNTFS)
flags |= GIT_PATH_REJECT_DOT_GIT_NTFS;
return flags;
}
-bool git_path_isvalid(
+bool git_path_validate(
git_repository *repo,
const char *path,
+ uint16_t mode,
unsigned int flags)
{
const char *start, *c;
return false;
if (*c == '/') {
- if (!verify_component(repo, start, (c - start), flags))
+ if (!verify_component(repo, start, (c - start), mode, flags))
return false;
start = c+1;
}
}
- return verify_component(repo, start, (c - start), flags);
+ return verify_component(repo, start, (c - start), mode, flags);
+}
+
+#ifdef GIT_WIN32
+GIT_INLINE(bool) should_validate_longpaths(git_repository *repo)
+{
+ int longpaths = 0;
+
+ if (repo &&
+ git_repository__configmap_lookup(&longpaths, repo, GIT_CONFIGMAP_LONGPATHS) < 0)
+ longpaths = 0;
+
+ return (longpaths == 0);
+}
+
+#else
+
+GIT_INLINE(bool) should_validate_longpaths(git_repository *repo)
+{
+ GIT_UNUSED(repo);
+
+ return false;
+}
+#endif
+
+int git_path_validate_workdir(git_repository *repo, const char *path)
+{
+ if (should_validate_longpaths(repo))
+ return git_path_validate_filesystem(path, strlen(path));
+
+ return 0;
+}
+
+int git_path_validate_workdir_with_len(
+ git_repository *repo,
+ const char *path,
+ size_t path_len)
+{
+ if (should_validate_longpaths(repo))
+ return git_path_validate_filesystem(path, path_len);
+
+ return 0;
+}
+
+int git_path_validate_workdir_buf(git_repository *repo, git_buf *path)
+{
+ return git_path_validate_workdir_with_len(repo, path->ptr, path->size);
}
int git_path_normalize_slashes(git_buf *out, const char *path)
return 0;
}
+
+static const struct {
+ const char *file;
+ const char *hash;
+ size_t filelen;
+} gitfiles[] = {
+ { "gitignore", "gi250a", CONST_STRLEN("gitignore") },
+ { "gitmodules", "gi7eba", CONST_STRLEN("gitmodules") },
+ { "gitattributes", "gi7d29", CONST_STRLEN("gitattributes") }
+};
+
+extern int git_path_is_gitfile(const char *path, size_t pathlen, git_path_gitfile gitfile, git_path_fs fs)
+{
+ const char *file, *hash;
+ size_t filelen;
+
+ if (!(gitfile >= GIT_PATH_GITFILE_GITIGNORE && gitfile < ARRAY_SIZE(gitfiles))) {
+ git_error_set(GIT_ERROR_OS, "invalid gitfile for path validation");
+ return -1;
+ }
+
+ file = gitfiles[gitfile].file;
+ filelen = gitfiles[gitfile].filelen;
+ hash = gitfiles[gitfile].hash;
+
+ switch (fs) {
+ case GIT_PATH_FS_GENERIC:
+ return !verify_dotgit_ntfs_generic(path, pathlen, file, filelen, hash) ||
+ !verify_dotgit_hfs_generic(path, pathlen, file, filelen);
+ case GIT_PATH_FS_NTFS:
+ return !verify_dotgit_ntfs_generic(path, pathlen, file, filelen, hash);
+ case GIT_PATH_FS_HFS:
+ return !verify_dotgit_hfs_generic(path, pathlen, file, filelen);
+ default:
+ git_error_set(GIT_ERROR_OS, "invalid filesystem for path validation");
+ return -1;
+ }
+}
+
+bool git_path_supports_symlinks(const char *dir)
+{
+ git_buf path = GIT_BUF_INIT;
+ bool supported = false;
+ struct stat st;
+ int fd;
+
+ if ((fd = git_futils_mktmp(&path, dir, 0666)) < 0 ||
+ p_close(fd) < 0 ||
+ p_unlink(path.ptr) < 0 ||
+ p_symlink("testing", path.ptr) < 0 ||
+ p_lstat(path.ptr, &st) < 0)
+ goto done;
+
+ supported = (S_ISLNK(st.st_mode) != 0);
+done:
+ if (path.size)
+ (void)p_unlink(path.ptr);
+ git_buf_dispose(&path);
+ return supported;
+}
+
+int git_path_validate_system_file_ownership(const char *path)
+{
+#ifndef GIT_WIN32
+ GIT_UNUSED(path);
+ return GIT_OK;
+#else
+ git_win32_path buf;
+ PSID owner_sid;
+ PSECURITY_DESCRIPTOR descriptor = NULL;
+ HANDLE token;
+ TOKEN_USER *info = NULL;
+ DWORD err, len;
+ int ret;
+
+ if (git_win32_path_from_utf8(buf, path) < 0)
+ return -1;
+
+ err = GetNamedSecurityInfoW(buf, SE_FILE_OBJECT,
+ OWNER_SECURITY_INFORMATION |
+ DACL_SECURITY_INFORMATION,
+ &owner_sid, NULL, NULL, NULL, &descriptor);
+
+ if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
+ ret = GIT_ENOTFOUND;
+ goto cleanup;
+ }
+
+ if (err != ERROR_SUCCESS) {
+ git_error_set(GIT_ERROR_OS, "failed to get security information");
+ ret = GIT_ERROR;
+ goto cleanup;
+ }
+
+ if (!IsValidSid(owner_sid)) {
+ git_error_set(GIT_ERROR_INVALID, "programdata configuration file owner is unknown");
+ ret = GIT_ERROR;
+ goto cleanup;
+ }
+
+ if (IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) ||
+ IsWellKnownSid(owner_sid, WinLocalSystemSid)) {
+ ret = GIT_OK;
+ goto cleanup;
+ }
+
+ /* Obtain current user's SID */
+ if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token) &&
+ !GetTokenInformation(token, TokenUser, NULL, 0, &len)) {
+ info = git__malloc(len);
+ GIT_ERROR_CHECK_ALLOC(info);
+ if (!GetTokenInformation(token, TokenUser, info, len, &len)) {
+ git__free(info);
+ info = NULL;
+ }
+ }
+
+ /*
+ * If the file is owned by the same account that is running the current
+ * process, it's okay to read from that file.
+ */
+ if (info && EqualSid(owner_sid, info->User.Sid))
+ ret = GIT_OK;
+ else {
+ git_error_set(GIT_ERROR_INVALID, "programdata configuration file owner is not valid");
+ ret = GIT_ERROR;
+ }
+ git__free(info);
+
+cleanup:
+ if (descriptor)
+ LocalFree(descriptor);
+
+ return ret;
+#endif
+}