summaryrefslogtreecommitdiff
path: root/src/path.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/path.c')
-rw-r--r--src/path.c2101
1 files changed, 0 insertions, 2101 deletions
diff --git a/src/path.c b/src/path.c
deleted file mode 100644
index c444b31a7..000000000
--- a/src/path.c
+++ /dev/null
@@ -1,2101 +0,0 @@
-/*
- * 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 "path.h"
-
-#include "posix.h"
-#include "repository.h"
-#ifdef GIT_WIN32
-#include "win32/posix.h"
-#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>
-
-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)
-{
- if (pos < 3)
- return false;
-
- if (path[0] != '/' || path[1] != '/')
- return false;
-
- while (pos-- > 2) {
- if (path[pos] == '/')
- return false;
- }
-
- return true;
-}
-#endif
-
-/*
- * Based on the Android implementation, BSD licensed.
- * http://android.git.kernel.org/
- *
- * Copyright (C) 2008 The Android Open Source Project
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in
- * the documentation and/or other materials provided with the
- * distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-int git_path_basename_r(git_buf *buffer, const char *path)
-{
- const char *endp, *startp;
- int len, result;
-
- /* Empty or NULL string gets treated as "." */
- if (path == NULL || *path == '\0') {
- startp = ".";
- len = 1;
- goto Exit;
- }
-
- /* Strip trailing slashes */
- endp = path + strlen(path) - 1;
- while (endp > path && *endp == '/')
- endp--;
-
- /* All slashes becomes "/" */
- if (endp == path && *endp == '/') {
- startp = "/";
- len = 1;
- goto Exit;
- }
-
- /* Find the start of the base */
- startp = endp;
- while (startp > path && *(startp - 1) != '/')
- startp--;
-
- /* Cast is safe because max path < max int */
- len = (int)(endp - startp + 1);
-
-Exit:
- result = len;
-
- if (buffer != NULL && git_buf_set(buffer, startp, len) < 0)
- return -1;
-
- 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 is_prefix = 0, len;
-
- /* Empty or NULL string gets treated as "." */
- if (path == NULL || *path == '\0') {
- path = ".";
- len = 1;
- goto Exit;
- }
-
- /* Strip trailing slashes */
- endp = path + strlen(path) - 1;
- 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--;
-
- /* Either the dir is "/" or there are no slashes */
- if (endp == path) {
- path = (*endp == '/') ? "/" : ".";
- len = 1;
- goto Exit;
- }
-
- do {
- endp--;
- } while (endp > path && *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;
- }
-
- /* Cast is safe because max path < max int */
- len = (int)(endp - path + 1);
-
-Exit:
- if (buffer) {
- if (git_buf_set(buffer, path, len) < 0)
- return -1;
- if (is_prefix && git_buf_putc(buffer, '/') < 0)
- return -1;
- }
-
- return len;
-}
-
-
-char *git_path_dirname(const char *path)
-{
- git_buf buf = GIT_BUF_INIT;
- char *dirname;
-
- git_path_dirname_r(&buf, path);
- dirname = git_buf_detach(&buf);
- git_buf_dispose(&buf); /* avoid memleak if error occurs */
-
- return dirname;
-}
-
-char *git_path_basename(const char *path)
-{
- git_buf buf = GIT_BUF_INIT;
- char *basename;
-
- git_path_basename_r(&buf, path);
- basename = git_buf_detach(&buf);
- git_buf_dispose(&buf); /* avoid memleak if error occurs */
-
- return basename;
-}
-
-size_t git_path_basename_offset(git_buf *buffer)
-{
- ssize_t slash;
-
- if (!buffer || buffer->size <= 0)
- return 0;
-
- slash = git_buf_rfind_next(buffer, '/');
-
- if (slash >= 0 && buffer->ptr[slash] == '/')
- return (size_t)(slash + 1);
-
- return 0;
-}
-
-int git_path_root(const char *path)
-{
- int offset = 0, prefix_len;
-
- /* Does the root of the path look like a windows drive ? */
- if ((prefix_len = dos_drive_prefix_length(path)))
- offset += prefix_len;
-
-#ifdef GIT_WIN32
- /* Are we dealing with a windows network path? */
- else if ((path[0] == '/' && path[1] == '/' && path[2] != '/') ||
- (path[0] == '\\' && path[1] == '\\' && path[2] != '\\'))
- {
- offset += 2;
-
- /* Skip the computer name segment */
- while (path[offset] && path[offset] != '/' && path[offset] != '\\')
- offset++;
- }
-
- if (path[offset] == '\\')
- return offset;
-#endif
-
- if (path[offset] == '/')
- return offset;
-
- return -1; /* Not a real error - signals that path is not rooted */
-}
-
-static void path_trim_slashes(git_buf *path)
-{
- int ceiling = git_path_root(path->ptr) + 1;
-
- if (ceiling < 0)
- return;
-
- while (path->size > (size_t)ceiling) {
- if (path->ptr[path->size-1] != '/')
- break;
-
- path->ptr[path->size-1] = '\0';
- path->size--;
- }
-}
-
-int git_path_join_unrooted(
- git_buf *path_out, const char *path, const char *base, ssize_t *root_at)
-{
- ssize_t root;
-
- GIT_ASSERT_ARG(path_out);
- GIT_ASSERT_ARG(path);
-
- root = (ssize_t)git_path_root(path);
-
- if (base != NULL && root < 0) {
- if (git_buf_joinpath(path_out, base, path) < 0)
- return -1;
-
- root = (ssize_t)strlen(base);
- } else {
- if (git_buf_sets(path_out, path) < 0)
- return -1;
-
- if (root < 0)
- root = 0;
- else if (base)
- git_path_equal_or_prefixed(base, path, &root);
- }
-
- if (root_at)
- *root_at = root;
-
- 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];
-
- GIT_ASSERT_ARG(path_out);
- GIT_ASSERT_ARG(path);
-
- /* construct path if needed */
- if (base != NULL && git_path_root(path) < 0) {
- if (git_buf_joinpath(path_out, base, path) < 0)
- return -1;
- path = path_out->ptr;
- }
-
- if (p_realpath(path, buf) == NULL) {
- /* 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;
- git_error_set(GIT_ERROR_OS, "failed to resolve path '%s'", path);
-
- git_buf_clear(path_out);
-
- return error;
- }
-
- return git_buf_sets(path_out, buf);
-}
-
-int git_path_prettify_dir(git_buf *path_out, const char *path, const char *base)
-{
- int error = git_path_prettify(path_out, path, base);
- return (error < 0) ? error : git_path_to_dir(path_out);
-}
-
-int git_path_to_dir(git_buf *path)
-{
- if (path->asize > 0 &&
- git_buf_len(path) > 0 &&
- path->ptr[git_buf_len(path) - 1] != '/')
- git_buf_putc(path, '/');
-
- return git_buf_oom(path) ? -1 : 0;
-}
-
-void git_path_string_to_dir(char *path, size_t size)
-{
- size_t end = strlen(path);
-
- if (end && path[end - 1] != '/' && end < size) {
- path[end] = '/';
- path[end + 1] = '\0';
- }
-}
-
-int git__percent_decode(git_buf *decoded_out, const char *input)
-{
- int len, hi, lo, i;
-
- GIT_ASSERT_ARG(decoded_out);
- GIT_ASSERT_ARG(input);
-
- len = (int)strlen(input);
- git_buf_clear(decoded_out);
-
- for(i = 0; i < len; i++)
- {
- char c = input[i];
-
- if (c != '%')
- goto append;
-
- if (i >= len - 2)
- goto append;
-
- hi = git__fromhex(input[i + 1]);
- lo = git__fromhex(input[i + 2]);
-
- if (hi < 0 || lo < 0)
- goto append;
-
- c = (char)(hi << 4 | lo);
- i += 2;
-
-append:
- if (git_buf_putc(decoded_out, c) < 0)
- return -1;
- }
-
- return 0;
-}
-
-static int error_invalid_local_file_uri(const char *uri)
-{
- git_error_set(GIT_ERROR_CONFIG, "'%s' is not a valid local file URI", uri);
- return -1;
-}
-
-static int local_file_url_prefixlen(const char *file_url)
-{
- int len = -1;
-
- if (git__prefixcmp(file_url, "file://") == 0) {
- if (file_url[7] == '/')
- len = 8;
- else if (git__prefixcmp(file_url + 7, "localhost/") == 0)
- len = 17;
- }
-
- return len;
-}
-
-bool git_path_is_local_file_url(const char *file_url)
-{
- return (local_file_url_prefixlen(file_url) > 0);
-}
-
-int git_path_fromurl(git_buf *local_path_out, const char *file_url)
-{
- int offset;
-
- 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] == '/')
- return error_invalid_local_file_uri(file_url);
-
-#ifndef GIT_WIN32
- offset--; /* A *nix absolute path starts with a forward slash */
-#endif
-
- git_buf_clear(local_path_out);
- return git__percent_decode(local_path_out, file_url + offset);
-}
-
-int git_path_walk_up(
- git_buf *path,
- const char *ceiling,
- int (*cb)(void *data, const char *),
- void *data)
-{
- int error = 0;
- git_buf iter;
- ssize_t stop = 0, scan;
- char oldc = '\0';
-
- GIT_ASSERT_ARG(path);
- GIT_ASSERT_ARG(cb);
-
- if (ceiling != NULL) {
- if (git__prefixcmp(path->ptr, ceiling) == 0)
- stop = (ssize_t)strlen(ceiling);
- else
- stop = git_buf_len(path);
- }
- scan = git_buf_len(path);
-
- /* empty path: yield only once */
- if (!scan) {
- error = cb(data, "");
- if (error)
- git_error_set_after_callback(error);
- return error;
- }
-
- iter.ptr = path->ptr;
- iter.size = git_buf_len(path);
- iter.asize = path->asize;
-
- while (scan >= stop) {
- error = cb(data, iter.ptr);
- iter.ptr[scan] = oldc;
-
- if (error) {
- git_error_set_after_callback(error);
- break;
- }
-
- scan = git_buf_rfind_next(&iter, '/');
- if (scan >= 0) {
- scan++;
- oldc = iter.ptr[scan];
- iter.size = scan;
- iter.ptr[scan] = '\0';
- }
- }
-
- if (scan >= 0)
- iter.ptr[scan] = oldc;
-
- /* relative path: yield for the last component */
- if (!error && stop == 0 && iter.ptr[0] != '/') {
- error = cb(data, "");
- if (error)
- git_error_set_after_callback(error);
- }
-
- return error;
-}
-
-bool git_path_exists(const char *path)
-{
- GIT_ASSERT_ARG_WITH_RETVAL(path, false);
- return p_access(path, F_OK) == 0;
-}
-
-bool git_path_isdir(const char *path)
-{
- struct stat st;
- if (p_stat(path, &st) < 0)
- return false;
-
- return S_ISDIR(st.st_mode) != 0;
-}
-
-bool git_path_isfile(const char *path)
-{
- struct stat st;
-
- 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)
-{
- 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);
-
- /* FindFirstFile will fail if there are no children to the given
- * path, which can happen if the given path is a file (and obviously
- * has no children) or if the given path is an empty mount point.
- * (Most directories have at least directory entries '.' and '..',
- * but ridiculously another volume mounted in another drive letter's
- * path space do not, and thus have nothing to enumerate.) If
- * FindFirstFile fails, check if this is a directory-like thing
- * (a mount point).
- */
- if (hFind == INVALID_HANDLE_VALUE)
- return git_path_isdir(path);
-
- /* If the find handle was created successfully, then it's a directory */
- 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);
- }
-
- return empty;
-}
-
-#else
-
-static int path_found_entry(void *payload, git_buf *path)
-{
- GIT_UNUSED(payload);
- return !git_path_is_dot_or_dotdot(path->ptr);
-}
-
-bool git_path_is_empty_dir(const char *path)
-{
- int error;
- git_buf dir = GIT_BUF_INIT;
-
- if (!git_path_isdir(path))
- return false;
-
- if ((error = git_buf_sets(&dir, path)) != 0)
- git_error_clear();
- else
- error = git_path_direach(&dir, 0, path_found_entry, NULL);
-
- git_buf_dispose(&dir);
-
- return !error;
-}
-
-#endif
-
-int git_path_set_error(int errno_value, const char *path, const char *action)
-{
- switch (errno_value) {
- case ENOENT:
- case ENOTDIR:
- git_error_set(GIT_ERROR_OS, "could not find '%s' to %s", path, action);
- return GIT_ENOTFOUND;
-
- case EINVAL:
- case ENAMETOOLONG:
- git_error_set(GIT_ERROR_OS, "invalid path for filesystem '%s'", path);
- return GIT_EINVALIDSPEC;
-
- case EEXIST:
- 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:
- git_error_set(GIT_ERROR_OS, "could not %s '%s'", action, path);
- return -1;
- }
-}
-
-int git_path_lstat(const char *path, struct stat *st)
-{
- if (p_lstat(path, st) == 0)
- return 0;
-
- return git_path_set_error(errno, path, "stat");
-}
-
-static bool _check_dir_contents(
- git_buf *dir,
- const char *sub,
- bool (*predicate)(const char *))
-{
- bool result;
- size_t dir_size = git_buf_len(dir);
- size_t sub_size = strlen(sub);
- size_t alloc_size;
-
- /* leave base valid even if we could not make space for subdir */
- if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, dir_size, sub_size) ||
- GIT_ADD_SIZET_OVERFLOW(&alloc_size, alloc_size, 2) ||
- git_buf_try_grow(dir, alloc_size, false) < 0)
- return false;
-
- /* save excursion */
- if (git_buf_joinpath(dir, dir->ptr, sub) < 0)
- return false;
-
- result = predicate(dir->ptr);
-
- /* restore path */
- git_buf_truncate(dir, dir_size);
- return result;
-}
-
-bool git_path_contains(git_buf *dir, const char *item)
-{
- return _check_dir_contents(dir, item, &git_path_exists);
-}
-
-bool git_path_contains_dir(git_buf *base, const char *subdir)
-{
- return _check_dir_contents(base, subdir, &git_path_isdir);
-}
-
-bool git_path_contains_file(git_buf *base, const char *file)
-{
- return _check_dir_contents(base, file, &git_path_isfile);
-}
-
-int git_path_find_dir(git_buf *dir)
-{
- int error = 0;
- char buf[GIT_PATH_MAX];
-
- 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) */
- error = (git_path_dirname_r(dir, dir->ptr) < 0) ? -1 : 0;
-
- if (!error)
- error = git_path_to_dir(dir);
-
- return error;
-}
-
-int git_path_resolve_relative(git_buf *path, size_t ceiling)
-{
- char *base, *to, *from, *next;
- size_t len;
-
- GIT_ERROR_CHECK_ALLOC_BUF(path);
-
- if (ceiling > path->size)
- ceiling = path->size;
-
- /* recognize drive prefixes, etc. that should not be backed over */
- if (ceiling == 0)
- ceiling = git_path_root(path->ptr) + 1;
-
- /* recognize URL prefixes that should not be backed over */
- if (ceiling == 0) {
- for (next = path->ptr; *next && git__isalpha(*next); ++next);
- if (next[0] == ':' && next[1] == '/' && next[2] == '/')
- ceiling = (next + 3) - path->ptr;
- }
-
- base = to = from = path->ptr + ceiling;
-
- while (*from) {
- for (next = from; *next && *next != '/'; ++next);
-
- len = next - from;
-
- if (len == 1 && from[0] == '.')
- /* do nothing with singleton dot */;
-
- else if (len == 2 && from[0] == '.' && from[1] == '.') {
- /* error out if trying to up one from a hard base */
- if (to == base && ceiling != 0) {
- git_error_set(GIT_ERROR_INVALID,
- "cannot strip root component off url");
- return -1;
- }
-
- /* no more path segments to strip,
- * use '../' as a new base path */
- if (to == base) {
- if (*next == '/')
- len++;
-
- if (to != from)
- memmove(to, from, len);
-
- to += len;
- /* this is now the base, can't back up from a
- * relative prefix */
- base = to;
- } else {
- /* back up a path segment */
- while (to > base && to[-1] == '/') to--;
- while (to > base && to[-1] != '/') to--;
- }
- } else {
- if (*next == '/' && *from != '/')
- len++;
-
- if (to != from)
- memmove(to, from, len);
-
- to += len;
- }
-
- from += len;
-
- while (*from == '/') from++;
- }
-
- *to = '\0';
-
- path->size = to - path->ptr;
-
- return 0;
-}
-
-int git_path_apply_relative(git_buf *target, const char *relpath)
-{
- return git_buf_joinpath(target, git_buf_cstr(target), relpath) ||
- git_path_resolve_relative(target, 0);
-}
-
-int git_path_cmp(
- const char *name1, size_t len1, int isdir1,
- const char *name2, size_t len2, int isdir2,
- int (*compare)(const char *, const char *, size_t))
-{
- unsigned char c1, c2;
- size_t len = len1 < len2 ? len1 : len2;
- int cmp;
-
- cmp = compare(name1, name2, len);
- if (cmp)
- return cmp;
-
- c1 = name1[len];
- c2 = name2[len];
-
- if (c1 == '\0' && isdir1)
- c1 = '/';
-
- if (c2 == '\0' && isdir2)
- c2 = '/';
-
- 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;
- size_t plen = path->size, newlen, alloclen, depth = 1, i, offset;
-
- for (p_dirsep = p = path->ptr, q_dirsep = q = parent; *p && *q; p++, q++) {
- if (*p == '/' && *q == '/') {
- p_dirsep = p;
- q_dirsep = q;
- }
- else if (*p != *q)
- break;
- }
-
- /* need at least 1 common path segment */
- if ((p_dirsep == path->ptr || q_dirsep == parent) &&
- (*p_dirsep != '/' || *q_dirsep != '/')) {
- git_error_set(GIT_ERROR_INVALID,
- "%s is not a parent of %s", parent, path->ptr);
- return GIT_ENOTFOUND;
- }
-
- if (*p == '/' && !*q)
- p++;
- else if (!*p && *q == '/')
- q++;
- else if (!*p && !*q)
- return git_buf_clear(path), 0;
- else {
- p = p_dirsep + 1;
- q = q_dirsep + 1;
- }
-
- plen -= (p - path->ptr);
-
- if (!*q)
- return git_buf_set(path, p, plen);
-
- for (; (q = strchr(q, '/')) && *(q + 1); q++)
- depth++;
-
- GIT_ERROR_CHECK_ALLOC_MULTIPLY(&newlen, depth, 3);
- GIT_ERROR_CHECK_ALLOC_ADD(&newlen, newlen, plen);
-
- GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, newlen, 1);
-
- /* save the offset as we might realllocate the pointer */
- offset = p - path->ptr;
- if (git_buf_try_grow(path, alloclen, 1) < 0)
- return -1;
- p = path->ptr + offset;
-
- memmove(path->ptr + (depth * 3), p, plen + 1);
-
- for (i = 0; i < depth; i++)
- memcpy(path->ptr + (i * 3), "../", 3);
-
- path->size = newlen;
- return 0;
-}
-
-bool git_path_has_non_ascii(const char *path, size_t pathlen)
-{
- const uint8_t *scan = (const uint8_t *)path, *end;
-
- for (end = scan + pathlen; scan < end; ++scan)
- if (*scan & 0x80)
- return true;
-
- return false;
-}
-
-#ifdef GIT_USE_ICONV
-
-int git_path_iconv_init_precompose(git_path_iconv_t *ic)
-{
- git_buf_init(&ic->buf, 0);
- ic->map = iconv_open(GIT_PATH_REPO_ENCODING, GIT_PATH_NATIVE_ENCODING);
- return 0;
-}
-
-void git_path_iconv_clear(git_path_iconv_t *ic)
-{
- if (ic) {
- if (ic->map != (iconv_t)-1)
- iconv_close(ic->map);
- git_buf_dispose(&ic->buf);
- }
-}
-
-int git_path_iconv(git_path_iconv_t *ic, const char **in, size_t *inlen)
-{
- char *nfd = (char*)*in, *nfc;
- size_t nfdlen = *inlen, nfclen, wantlen = nfdlen, alloclen, rv;
- int retry = 1;
-
- if (!ic || ic->map == (iconv_t)-1 ||
- !git_path_has_non_ascii(*in, *inlen))
- return 0;
-
- git_buf_clear(&ic->buf);
-
- while (1) {
- GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, wantlen, 1);
- if (git_buf_grow(&ic->buf, alloclen) < 0)
- return -1;
-
- nfc = ic->buf.ptr + ic->buf.size;
- nfclen = ic->buf.asize - ic->buf.size;
-
- rv = iconv(ic->map, &nfd, &nfdlen, &nfc, &nfclen);
-
- ic->buf.size = (nfc - ic->buf.ptr);
-
- if (rv != (size_t)-1)
- break;
-
- /* if we cannot convert the data (probably because iconv thinks
- * it is not valid UTF-8 source data), then use original data
- */
- if (errno != E2BIG)
- return 0;
-
- /* make space for 2x the remaining data to be converted
- * (with per retry overhead to avoid infinite loops)
- */
- wantlen = ic->buf.size + max(nfclen, nfdlen) * 2 + (size_t)(retry * 4);
-
- if (retry++ > 4)
- goto fail;
- }
-
- ic->buf.ptr[ic->buf.size] = '\0';
-
- *in = ic->buf.ptr;
- *inlen = ic->buf.size;
-
- return 0;
-
-fail:
- git_error_set(GIT_ERROR_OS, "unable to convert unicode path data");
- return -1;
-}
-
-static const char *nfc_file = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D.XXXXXX";
-static const char *nfd_file = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D.XXXXXX";
-
-/* Check if the platform is decomposing unicode data for us. We will
- * emulate core Git and prefer to use precomposed unicode data internally
- * on these platforms, composing the decomposed unicode on the fly.
- *
- * This mainly happens on the Mac where HDFS stores filenames as
- * decomposed unicode. Even on VFAT and SAMBA file systems, the Mac will
- * return decomposed unicode from readdir() even when the actual
- * filesystem is storing precomposed unicode.
- */
-bool git_path_does_fs_decompose_unicode(const char *root)
-{
- git_buf path = GIT_BUF_INIT;
- int fd;
- bool found_decomposed = false;
- char tmp[6];
-
- /* Create a file using a precomposed path and then try to find it
- * using the decomposed name. If the lookup fails, then we will mark
- * that we should precompose unicode for this repository.
- */
- if (git_buf_joinpath(&path, root, nfc_file) < 0 ||
- (fd = p_mkstemp(path.ptr)) < 0)
- goto done;
- p_close(fd);
-
- /* record trailing digits generated by mkstemp */
- memcpy(tmp, path.ptr + path.size - sizeof(tmp), sizeof(tmp));
-
- /* try to look up as NFD path */
- if (git_buf_joinpath(&path, root, nfd_file) < 0)
- goto done;
- memcpy(path.ptr + path.size - sizeof(tmp), tmp, sizeof(tmp));
-
- found_decomposed = git_path_exists(path.ptr);
-
- /* remove temporary file (using original precomposed path) */
- if (git_buf_joinpath(&path, root, nfc_file) < 0)
- goto done;
- memcpy(path.ptr + path.size - sizeof(tmp), tmp, sizeof(tmp));
-
- (void)p_unlink(path.ptr);
-
-done:
- git_buf_dispose(&path);
- return found_decomposed;
-}
-
-#else
-
-bool git_path_does_fs_decompose_unicode(const char *root)
-{
- GIT_UNUSED(root);
- return false;
-}
-
-#endif
-
-#if defined(__sun) || defined(__GNU__)
-typedef char path_dirent_data[sizeof(struct dirent) + FILENAME_MAX + 1];
-#else
-typedef struct dirent path_dirent_data;
-#endif
-
-int git_path_direach(
- git_buf *path,
- uint32_t flags,
- int (*fn)(void *, git_buf *),
- void *arg)
-{
- int error = 0;
- ssize_t wd_len;
- DIR *dir;
- struct dirent *de;
-
-#ifdef GIT_USE_ICONV
- git_path_iconv_t ic = GIT_PATH_ICONV_INIT;
-#endif
-
- GIT_UNUSED(flags);
-
- if (git_path_to_dir(path) < 0)
- return -1;
-
- wd_len = git_buf_len(path);
-
- if ((dir = opendir(path->ptr)) == NULL) {
- git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path->ptr);
- if (errno == ENOENT)
- return GIT_ENOTFOUND;
-
- return -1;
- }
-
-#ifdef GIT_USE_ICONV
- if ((flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0)
- (void)git_path_iconv_init_precompose(&ic);
-#endif
-
- while ((de = readdir(dir)) != NULL) {
- const char *de_path = de->d_name;
- size_t de_len = strlen(de_path);
-
- if (git_path_is_dot_or_dotdot(de_path))
- continue;
-
-#ifdef GIT_USE_ICONV
- if ((error = git_path_iconv(&ic, &de_path, &de_len)) < 0)
- break;
-#endif
-
- if ((error = git_buf_put(path, de_path, de_len)) < 0)
- break;
-
- 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 (!git_error_last())
- git_error_set_after_callback(error);
-
- break;
- }
- }
-
- closedir(dir);
-
-#ifdef GIT_USE_ICONV
- git_path_iconv_clear(&ic);
-#endif
-
- return error;
-}
-
-#if defined(GIT_WIN32) && !defined(__MINGW32__)
-
-/* Using _FIND_FIRST_EX_LARGE_FETCH may increase performance in Windows 7
- * and better.
- */
-#ifndef FIND_FIRST_EX_LARGE_FETCH
-# define FIND_FIRST_EX_LARGE_FETCH 2
-#endif
-
-int git_path_diriter_init(
- git_path_diriter *diriter,
- const char *path,
- unsigned int flags)
-{
- git_win32_path path_filter;
-
- static int is_win7_or_later = -1;
- if (is_win7_or_later < 0)
- is_win7_or_later = git_has_win32_version(6, 1, 0);
-
- 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;
-
- path_trim_slashes(&diriter->path_utf8);
-
- if (diriter->path_utf8.size == 0) {
- 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)) {
- git_error_set(GIT_ERROR_OS, "could not parse the directory path '%s'", path);
- return -1;
- }
-
- diriter->handle = FindFirstFileExW(
- path_filter,
- is_win7_or_later ? FindExInfoBasic : FindExInfoStandard,
- &diriter->current,
- FindExSearchNameMatch,
- NULL,
- is_win7_or_later ? FIND_FIRST_EX_LARGE_FETCH : 0);
-
- if (diriter->handle == INVALID_HANDLE_VALUE) {
- git_error_set(GIT_ERROR_OS, "could not open directory '%s'", path);
- return -1;
- }
-
- diriter->parent_utf8_len = diriter->path_utf8.size;
- diriter->flags = flags;
- return 0;
-}
-
-static int diriter_update_paths(git_path_diriter *diriter)
-{
- size_t filename_len, path_len;
-
- filename_len = wcslen(diriter->current.cFileName);
-
- if (GIT_ADD_SIZET_OVERFLOW(&path_len, diriter->parent_len, filename_len) ||
- GIT_ADD_SIZET_OVERFLOW(&path_len, path_len, 2))
- return -1;
-
- if (path_len > GIT_WIN_PATH_UTF16) {
- git_error_set(GIT_ERROR_FILESYSTEM,
- "invalid path '%.*ls\\%ls' (path too long)",
- diriter->parent_len, diriter->path, diriter->current.cFileName);
- return -1;
- }
-
- diriter->path[diriter->parent_len] = L'\\';
- memcpy(&diriter->path[diriter->parent_len+1],
- diriter->current.cFileName, filename_len * sizeof(wchar_t));
- diriter->path[path_len-1] = L'\0';
-
- git_buf_truncate(&diriter->path_utf8, diriter->parent_utf8_len);
-
- if (diriter->parent_utf8_len > 0 &&
- diriter->path_utf8.ptr[diriter->parent_utf8_len-1] != '/')
- git_buf_putc(&diriter->path_utf8, '/');
-
- git_buf_put_w(&diriter->path_utf8, diriter->current.cFileName, filename_len);
-
- if (git_buf_oom(&diriter->path_utf8))
- return -1;
-
- return 0;
-}
-
-int git_path_diriter_next(git_path_diriter *diriter)
-{
- bool skip_dot = !(diriter->flags & GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT);
-
- do {
- /* Our first time through, we already have the data from
- * FindFirstFileW. Use it, otherwise get the next file.
- */
- if (!diriter->needs_next)
- diriter->needs_next = 1;
- else if (!FindNextFileW(diriter->handle, &diriter->current))
- return GIT_ITEROVER;
- } while (skip_dot && git_path_is_dot_or_dotdotW(diriter->current.cFileName));
-
- if (diriter_update_paths(diriter) < 0)
- return -1;
-
- return 0;
-}
-
-int git_path_diriter_filename(
- const char **out,
- size_t *out_len,
- git_path_diriter *diriter)
-{
- 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;
- return 0;
-}
-
-int git_path_diriter_fullpath(
- const char **out,
- size_t *out_len,
- git_path_diriter *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;
- return 0;
-}
-
-int git_path_diriter_stat(struct stat *out, git_path_diriter *diriter)
-{
- GIT_ASSERT_ARG(out);
- GIT_ASSERT_ARG(diriter);
-
- return git_win32__file_attribute_to_stat(out,
- (WIN32_FILE_ATTRIBUTE_DATA *)&diriter->current,
- diriter->path);
-}
-
-void git_path_diriter_free(git_path_diriter *diriter)
-{
- if (diriter == NULL)
- return;
-
- git_buf_dispose(&diriter->path_utf8);
-
- if (diriter->handle != INVALID_HANDLE_VALUE) {
- FindClose(diriter->handle);
- diriter->handle = INVALID_HANDLE_VALUE;
- }
-}
-
-#else
-
-int git_path_diriter_init(
- git_path_diriter *diriter,
- const char *path,
- unsigned int flags)
-{
- 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;
-
- path_trim_slashes(&diriter->path);
-
- if (diriter->path.size == 0) {
- git_error_set(GIT_ERROR_FILESYSTEM, "could not open directory '%s'", path);
- return -1;
- }
-
- if ((diriter->dir = opendir(diriter->path.ptr)) == NULL) {
- git_buf_dispose(&diriter->path);
-
- git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path);
- return -1;
- }
-
-#ifdef GIT_USE_ICONV
- if ((flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0)
- (void)git_path_iconv_init_precompose(&diriter->ic);
-#endif
-
- diriter->parent_len = diriter->path.size;
- diriter->flags = flags;
-
- return 0;
-}
-
-int git_path_diriter_next(git_path_diriter *diriter)
-{
- struct dirent *de;
- const char *filename;
- size_t filename_len;
- bool skip_dot = !(diriter->flags & GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT);
- int error = 0;
-
- GIT_ASSERT_ARG(diriter);
-
- errno = 0;
-
- do {
- if ((de = readdir(diriter->dir)) == NULL) {
- if (!errno)
- return GIT_ITEROVER;
-
- 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));
-
- filename = de->d_name;
- filename_len = strlen(filename);
-
-#ifdef GIT_USE_ICONV
- if ((diriter->flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0 &&
- (error = git_path_iconv(&diriter->ic, &filename, &filename_len)) < 0)
- return error;
-#endif
-
- git_buf_truncate(&diriter->path, diriter->parent_len);
-
- 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))
- return -1;
-
- return error;
-}
-
-int git_path_diriter_filename(
- const char **out,
- size_t *out_len,
- git_path_diriter *diriter)
-{
- 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;
- return 0;
-}
-
-int git_path_diriter_fullpath(
- const char **out,
- size_t *out_len,
- git_path_diriter *diriter)
-{
- GIT_ASSERT_ARG(out);
- GIT_ASSERT_ARG(out_len);
- GIT_ASSERT_ARG(diriter);
-
- *out = diriter->path.ptr;
- *out_len = diriter->path.size;
- return 0;
-}
-
-int git_path_diriter_stat(struct stat *out, git_path_diriter *diriter)
-{
- GIT_ASSERT_ARG(out);
- GIT_ASSERT_ARG(diriter);
-
- return git_path_lstat(diriter->path.ptr, out);
-}
-
-void git_path_diriter_free(git_path_diriter *diriter)
-{
- if (diriter == NULL)
- return;
-
- if (diriter->dir) {
- closedir(diriter->dir);
- diriter->dir = NULL;
- }
-
-#ifdef GIT_USE_ICONV
- git_path_iconv_clear(&diriter->ic);
-#endif
-
- git_buf_dispose(&diriter->path);
-}
-
-#endif
-
-int git_path_dirload(
- git_vector *contents,
- const char *path,
- size_t prefix_len,
- uint32_t flags)
-{
- git_path_diriter iter = GIT_PATH_DIRITER_INIT;
- const char *name;
- size_t name_len;
- char *dup;
- int error;
-
- GIT_ASSERT_ARG(contents);
- GIT_ASSERT_ARG(path);
-
- if ((error = git_path_diriter_init(&iter, path, flags)) < 0)
- return error;
-
- while ((error = git_path_diriter_next(&iter)) == 0) {
- if ((error = git_path_diriter_fullpath(&name, &name_len, &iter)) < 0)
- break;
-
- GIT_ASSERT(name_len > prefix_len);
-
- dup = git__strndup(name + prefix_len, name_len - prefix_len);
- GIT_ERROR_CHECK_ALLOC(dup);
-
- if ((error = git_vector_insert(contents, dup)) < 0)
- break;
- }
-
- if (error == GIT_ITEROVER)
- error = 0;
-
- git_path_diriter_free(&iter);
- return error;
-}
-
-int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path)
-{
- if (git_path_is_local_file_url(url_or_path))
- return git_path_fromurl(local_path_out, url_or_path);
- else
- return git_buf_sets(local_path_out, url_or_path);
-}
-
-/* Reject paths like AUX or COM1, or those versions that end in a dot or
- * colon. ("AUX." or "AUX:")
- */
-GIT_INLINE(bool) verify_dospath(
- const char *component,
- size_t len,
- const char dospath[3],
- bool trailing_num)
-{
- size_t last = trailing_num ? 4 : 3;
-
- if (len < last || git__strncasecmp(component, dospath, 3) != 0)
- return true;
-
- if (trailing_num && (component[3] < '1' || component[3] > '9'))
- return true;
-
- return (len > last &&
- component[last] != '.' &&
- component[last] != ':');
-}
-
-static int32_t next_hfs_char(const char **in, size_t *len)
-{
- while (*len) {
- uint32_t codepoint;
- int cp_len = git_utf8_iterate(&codepoint, *in, *len);
- if (cp_len < 0)
- return -1;
-
- (*in) += cp_len;
- (*len) -= cp_len;
-
- /* these code points are ignored completely */
- switch (codepoint) {
- case 0x200c: /* ZERO WIDTH NON-JOINER */
- case 0x200d: /* ZERO WIDTH JOINER */
- case 0x200e: /* LEFT-TO-RIGHT MARK */
- case 0x200f: /* RIGHT-TO-LEFT MARK */
- case 0x202a: /* LEFT-TO-RIGHT EMBEDDING */
- case 0x202b: /* RIGHT-TO-LEFT EMBEDDING */
- case 0x202c: /* POP DIRECTIONAL FORMATTING */
- case 0x202d: /* LEFT-TO-RIGHT OVERRIDE */
- case 0x202e: /* RIGHT-TO-LEFT OVERRIDE */
- case 0x206a: /* INHIBIT SYMMETRIC SWAPPING */
- case 0x206b: /* ACTIVATE SYMMETRIC SWAPPING */
- case 0x206c: /* INHIBIT ARABIC FORM SHAPING */
- case 0x206d: /* ACTIVATE ARABIC FORM SHAPING */
- case 0x206e: /* NATIONAL DIGIT SHAPES */
- case 0x206f: /* NOMINAL DIGIT SHAPES */
- case 0xfeff: /* ZERO WIDTH NO-BREAK SPACE */
- continue;
- }
-
- /* fold into lowercase -- this will only fold characters in
- * the ASCII range, which is perfectly fine, because the
- * git folder name can only be composed of ascii characters
- */
- return git__tolower((int)codepoint);
- }
- return 0; /* NULL byte -- end of string */
-}
-
-static bool verify_dotgit_hfs_generic(const char *path, size_t len, const char *needle, size_t needle_len)
-{
- 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;
- size_t reserved_len = git_repository__reserved_names_win32_len;
- size_t start = 0, i;
-
- if (repo)
- git_repository__reserved_names(&reserved, &reserved_len, repo, true);
-
- for (i = 0; i < reserved_len; i++) {
- git_buf *r = &reserved[i];
-
- if (len >= r->size &&
- strncasecmp(path, r->ptr, r->size) == 0) {
- start = r->size;
- break;
- }
- }
-
- if (!start)
- return true;
-
- /*
- * 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.' */
- for (i = start; i < len; i++) {
- if (path[i] != ' ' && path[i] != '.')
- return true;
- }
-
- 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 false;
-
- if ((flags & GIT_PATH_REJECT_SLASH) && c == '/')
- return false;
-
- if (flags & GIT_PATH_REJECT_NT_CHARS) {
- if (c < 32)
- return false;
-
- switch (c) {
- case '<':
- case '>':
- case ':':
- case '"':
- case '|':
- case '?':
- case '*':
- return false;
- }
- }
-
- return true;
-}
-
-/*
- * 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
- * anywhere, we want to avoid writing weird paths on Windows that can't
- * be handled by tools that use the non-\\?\ APIs, we don't want slashes
- * or double slashes at the end of paths that can make them ambiguous.
- *
- * For checkout, we don't want to recurse into ".git" either.
- */
-static bool verify_component(
- git_repository *repo,
- const char *component,
- size_t len,
- uint16_t mode,
- unsigned int flags)
-{
- if (len == 0)
- return false;
-
- if ((flags & GIT_PATH_REJECT_TRAVERSAL) &&
- len == 1 && component[0] == '.')
- return false;
-
- if ((flags & GIT_PATH_REJECT_TRAVERSAL) &&
- len == 2 && component[0] == '.' && component[1] == '.')
- return false;
-
- if ((flags & GIT_PATH_REJECT_TRAILING_DOT) && component[len-1] == '.')
- return false;
-
- if ((flags & GIT_PATH_REJECT_TRAILING_SPACE) && component[len-1] == ' ')
- return false;
-
- if ((flags & GIT_PATH_REJECT_TRAILING_COLON) && component[len-1] == ':')
- return false;
-
- if (flags & GIT_PATH_REJECT_DOS_PATHS) {
- if (!verify_dospath(component, len, "CON", false) ||
- !verify_dospath(component, len, "PRN", false) ||
- !verify_dospath(component, len, "AUX", false) ||
- !verify_dospath(component, len, "NUL", false) ||
- !verify_dospath(component, len, "COM", true) ||
- !verify_dospath(component, len, "LPT", true))
- return false;
- }
-
- 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) {
- 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_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_INLINE(unsigned int) dotgit_flags(
- git_repository *repo,
- unsigned int flags)
-{
- int protectHFS = 0, protectNTFS = 1;
- int error = 0;
-
- flags |= GIT_PATH_REJECT_DOT_GIT_LITERAL;
-
-#ifdef __APPLE__
- protectHFS = 1;
-#endif
-
- if (repo && !protectHFS)
- error = git_repository__configmap_lookup(&protectHFS, repo, GIT_CONFIGMAP_PROTECTHFS);
- if (!error && protectHFS)
- flags |= GIT_PATH_REJECT_DOT_GIT_HFS;
-
- 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_validate(
- git_repository *repo,
- const char *path,
- uint16_t mode,
- unsigned int flags)
-{
- const char *start, *c;
-
- /* Upgrade the ".git" checks based on platform */
- if ((flags & GIT_PATH_REJECT_DOT_GIT))
- flags = dotgit_flags(repo, flags);
-
- for (start = c = path; *c; c++) {
- if (!verify_char(*c, flags))
- return false;
-
- if (*c == '/') {
- if (!verify_component(repo, start, (c - start), mode, flags))
- return false;
-
- start = c+1;
- }
- }
-
- 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)
-{
- int error;
- char *p;
-
- if ((error = git_buf_puts(out, path)) < 0)
- return error;
-
- for (p = out->ptr; *p; p++) {
- if (*p == '\\')
- *p = '/';
- }
-
- 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
-}