Commit a64119e3 by Edward Thomson

checkout: disallow bad paths on win32

Disallow:
 1. paths with trailing dot
 2. paths with trailing space
 3. paths with trailing colon
 4. paths that are 8.3 short names of .git folders ("GIT~1")
 5. paths that are reserved path names (COM1, LPT1, etc).
 6. paths with reserved DOS characters (colons, asterisks, etc)

These paths would (without \\?\ syntax) be elided to other paths - for
example, ".git." would be written as ".git".  As a result, writing these
paths literally (using \\?\ syntax) makes them hard to operate with from
the shell, Windows Explorer or other tools.  Disallow these.
parent 0d388adc
......@@ -1172,6 +1172,30 @@ static int checkout_get_remove_conflicts(
return checkout_conflicts_foreach(data, data->index, workdir, pathspec, checkout_conflict_append_remove, data);
}
static int checkout_verify_paths(
git_repository *repo,
int action,
git_diff_delta *delta)
{
unsigned int flags = GIT_PATH_REJECT_DEFAULTS | GIT_PATH_REJECT_DOT_GIT;
if (action & CHECKOUT_ACTION__REMOVE) {
if (!git_path_isvalid(repo, delta->old_file.path, flags)) {
giterr_set(GITERR_CHECKOUT, "Cannot remove invalid path '%s'", delta->old_file.path);
return -1;
}
}
if (action & ~CHECKOUT_ACTION__REMOVE) {
if (!git_path_isvalid(repo, delta->new_file.path, flags)) {
giterr_set(GITERR_CHECKOUT, "Cannot checkout to invalid path '%s'", delta->old_file.path);
return -1;
}
}
return 0;
}
static int checkout_get_actions(
uint32_t **actions_ptr,
size_t **counts_ptr,
......@@ -1205,7 +1229,9 @@ static int checkout_get_actions(
}
git_vector_foreach(deltas, i, delta) {
error = checkout_action(&act, data, delta, workdir, &wditem, &pathspec);
if ((error = checkout_action(&act, data, delta, workdir, &wditem, &pathspec)) == 0)
error = checkout_verify_paths(data->repo, act, delta);
if (error != 0)
goto fail;
......
......@@ -762,86 +762,19 @@ void git_index_entry__init_from_stat(
entry->file_size = st->st_size;
}
/*
* We fundamentally don't like some paths: we don't want
* dot or dot-dot anywhere, and for obvious reasons don't
* want to recurse into ".git" either.
*
* Also, we don't want double slashes or slashes at the
* end that can make pathnames ambiguous.
*/
static int verify_dotfile(const char *rest)
{
/*
* The first character was '.', but that
* has already been discarded, we now test
* the rest.
*/
/* "." is not allowed */
if (*rest == '\0' || *rest == '/')
return -1;
switch (*rest) {
/*
* ".git" followed by NUL or slash is bad. This
* shares the path end test with the ".." case.
*/
case 'g':
case 'G':
if (rest[1] != 'i' && rest[1] != 'I')
break;
if (rest[2] != 't' && rest[2] != 'T')
break;
rest += 2;
/* fallthrough */
case '.':
if (rest[1] == '\0' || rest[1] == '/')
return -1;
}
return 0;
}
static int verify_component(char c, const char *rest)
{
if ((c == '.' && verify_dotfile(rest)) < 0 || c == '/' || c == '\0') {
giterr_set(GITERR_INDEX, "Invalid path component in index: '%c%s'", c, rest);
return -1;
}
return 0;
}
static int verify_path(const char *path)
{
char c;
/* TODO: should we check this? */
/*
if (has_dos_drive_prefix(path))
return -1;
*/
c = *path++;
if (verify_component(c, path) < 0)
return -1;
while ((c = *path++) != '\0') {
if (c == '/') {
c = *path++;
if (verify_component(c, path) < 0)
return -1;
}
}
return 0;
}
static int index_entry_create(git_index_entry **out, const char *path)
static int index_entry_create(
git_index_entry **out,
git_repository *repo,
const char *path)
{
size_t pathlen = strlen(path);
struct entry_internal *entry;
if (verify_path(path) < 0)
if (!git_path_isvalid(repo, path,
GIT_PATH_REJECT_DEFAULTS | GIT_PATH_REJECT_DOT_GIT)) {
giterr_set(GITERR_INDEX, "Invalid path: '%s'", path);
return -1;
}
entry = git__calloc(sizeof(struct entry_internal) + pathlen + 1, 1);
GITERR_CHECK_ALLOC(entry);
......@@ -855,7 +788,9 @@ static int index_entry_create(git_index_entry **out, const char *path)
}
static int index_entry_init(
git_index_entry **entry_out, git_index *index, const char *rel_path)
git_index_entry **entry_out,
git_index *index,
const char *rel_path)
{
int error = 0;
git_index_entry *entry = NULL;
......@@ -867,7 +802,7 @@ static int index_entry_init(
"Could not initialize index entry. "
"Index is not backed up by an existing repository.");
if (index_entry_create(&entry, rel_path) < 0)
if (index_entry_create(&entry, INDEX_OWNER(index), rel_path) < 0)
return -1;
/* write the blob to disk and get the oid and stat info */
......@@ -933,7 +868,10 @@ static void index_entry_cpy(git_index_entry *tgt, const git_index_entry *src)
tgt->path = tgt_path; /* reset to existing path data */
}
static int index_entry_dup(git_index_entry **out, const git_index_entry *src)
static int index_entry_dup(
git_index_entry **out,
git_repository *repo,
const git_index_entry *src)
{
git_index_entry *entry;
......@@ -942,7 +880,7 @@ static int index_entry_dup(git_index_entry **out, const git_index_entry *src)
return 0;
}
if (index_entry_create(&entry, src->path) < 0)
if (index_entry_create(&entry, repo, src->path) < 0)
return -1;
index_entry_cpy(entry, src);
......@@ -1211,7 +1149,7 @@ int git_index_add(git_index *index, const git_index_entry *source_entry)
return -1;
}
if ((ret = index_entry_dup(&entry, source_entry)) < 0 ||
if ((ret = index_entry_dup(&entry, INDEX_OWNER(index), source_entry)) < 0 ||
(ret = index_insert(index, &entry, 1)) < 0)
return ret;
......@@ -1331,9 +1269,9 @@ int git_index_conflict_add(git_index *index,
assert (index);
if ((ret = index_entry_dup(&entries[0], ancestor_entry)) < 0 ||
(ret = index_entry_dup(&entries[1], our_entry)) < 0 ||
(ret = index_entry_dup(&entries[2], their_entry)) < 0)
if ((ret = index_entry_dup(&entries[0], INDEX_OWNER(index), ancestor_entry)) < 0 ||
(ret = index_entry_dup(&entries[1], INDEX_OWNER(index), our_entry)) < 0 ||
(ret = index_entry_dup(&entries[2], INDEX_OWNER(index), their_entry)) < 0)
goto on_error;
for (i = 0; i < 3; i++) {
......@@ -1850,7 +1788,10 @@ static int read_conflict_names(git_index *index, const char *buffer, size_t size
}
static size_t read_entry(
git_index_entry **out, const void *buffer, size_t buffer_size)
git_index_entry **out,
git_index *index,
const void *buffer,
size_t buffer_size)
{
size_t path_length, entry_size;
const char *path_ptr;
......@@ -1914,7 +1855,7 @@ static size_t read_entry(
entry.path = (char *)path_ptr;
if (index_entry_dup(out, &entry) < 0)
if (index_entry_dup(out, INDEX_OWNER(index), &entry) < 0)
return 0;
return entry_size;
......@@ -2015,7 +1956,7 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
/* Parse all the entries */
for (i = 0; i < header.entry_count && buffer_size > INDEX_FOOTER_SIZE; ++i) {
git_index_entry *entry;
size_t entry_size = read_entry(&entry, buffer, buffer_size);
size_t entry_size = read_entry(&entry, index, buffer, buffer_size);
/* 0 bytes read means an object corruption */
if (entry_size == 0) {
......@@ -2376,6 +2317,7 @@ int git_index_entry_stage(const git_index_entry *entry)
}
typedef struct read_tree_data {
git_index *index;
git_vector *old_entries;
git_vector *new_entries;
git_vector_cmp entry_cmp;
......@@ -2396,7 +2338,7 @@ static int read_tree_cb(
if (git_buf_joinpath(&path, root, tentry->filename) < 0)
return -1;
if (index_entry_create(&entry, path.ptr) < 0)
if (index_entry_create(&entry, INDEX_OWNER(data->index), path.ptr) < 0)
return -1;
entry->mode = tentry->attr;
......@@ -2437,6 +2379,7 @@ int git_index_read_tree(git_index *index, const git_tree *tree)
git_vector_set_cmp(&entries, index->entries._cmp); /* match sort */
data.index = index;
data.old_entries = &index->entries;
data.new_entries = &entries;
data.entry_cmp = index->entries_search;
......@@ -2556,7 +2499,7 @@ int git_index_add_all(
break;
/* make the new entry to insert */
if ((error = index_entry_dup(&entry, wd)) < 0)
if ((error = index_entry_dup(&entry, INDEX_OWNER(index), wd)) < 0)
break;
entry->id = blobid;
......
......@@ -7,6 +7,7 @@
#include "common.h"
#include "path.h"
#include "posix.h"
#include "repository.h"
#ifdef GIT_WIN32
#include "win32/posix.h"
#include "win32/w32_util.h"
......@@ -1238,3 +1239,150 @@ int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path)
else
return git_buf_sets(local_path_out, url_or_path);
}
GIT_INLINE(bool) verify_shortname(
git_repository *repo,
const char *component,
size_t len)
{
const char *shortname_repo;
if (len == git_repository__8dot3_default_len &&
strncasecmp(git_repository__8dot3_default, component, len) == 0)
return false;
if (repo &&
(shortname_repo = git_repository__8dot3_name(repo)) &&
shortname_repo != git_repository__8dot3_default &&
git__prefixncmp_icase(component, len, shortname_repo) == 0)
return false;
return true;
}
/* Reject paths like AUX or COM1, or those versions that end in a dot or
* colon. ("AUX." or "AUX:")
*/
GIT_INLINE(bool) verify_dospath(
const char *component,
size_t len,
const char dospath[3],
bool trailing_num)
{
size_t last = trailing_num ? 4 : 3;
if (len < last || git__strncasecmp(component, dospath, 3) != 0)
return true;
if (trailing_num && !git__isdigit(component[3]))
return true;
return (len > last &&
component[last] != '.' &&
component[last] != ':');
}
GIT_INLINE(bool) verify_char(unsigned char c, unsigned int flags)
{
if ((flags & GIT_PATH_REJECT_BACKSLASH) && c == '\\')
return false;
if (flags & GIT_PATH_REJECT_NT_CHARS) {
if (c < 32)
return false;
switch (c) {
case '<':
case '>':
case ':':
case '"':
case '|':
case '?':
case '*':
return false;
}
}
return true;
}
/*
* We fundamentally don't like some paths when dealing with user-inputted
* strings (in checkout or ref names): we don't want dot or dot-dot
* anywhere, we want to avoid writing weird paths on Windows that can't
* be handled by tools that use the non-\\?\ APIs, we don't want slashes
* or double slashes at the end of paths that can make them ambiguous.
*
* For checkout, we don't want to recurse into ".git" either.
*/
static bool verify_component(
git_repository *repo,
const char *component,
size_t len,
unsigned int flags)
{
if (len == 0)
return false;
if ((flags & GIT_PATH_REJECT_TRAVERSAL) &&
len == 1 && component[0] == '.')
return false;
if ((flags & GIT_PATH_REJECT_TRAVERSAL) &&
len == 2 && component[0] == '.' && component[1] == '.')
return false;
if ((flags & GIT_PATH_REJECT_DOT_GIT) && len == 4 &&
component[0] == '.' &&
(component[1] == 'g' || component[1] == 'G') &&
(component[2] == 'i' || component[2] == 'I') &&
(component[3] == 't' || component[3] == 'T'))
return false;
if ((flags & GIT_PATH_REJECT_TRAILING_DOT) && component[len-1] == '.')
return false;
if ((flags & GIT_PATH_REJECT_TRAILING_SPACE) && component[len-1] == ' ')
return false;
if ((flags & GIT_PATH_REJECT_TRAILING_COLON) && component[len-1] == ':')
return false;
if ((flags & GIT_PATH_REJECT_DOS_GIT_SHORTNAME) &&
!verify_shortname(repo, component, len))
return false;
if (flags & GIT_PATH_REJECT_DOS_PATHS) {
if (!verify_dospath(component, len, "CON", false) ||
!verify_dospath(component, len, "PRN", false) ||
!verify_dospath(component, len, "AUX", false) ||
!verify_dospath(component, len, "NUL", false) ||
!verify_dospath(component, len, "COM", true) ||
!verify_dospath(component, len, "LPT", true))
return false;
}
return true;
}
bool git_path_isvalid(
git_repository *repo,
const char *path,
unsigned int flags)
{
const char *start, *c;
for (start = c = path; *c; c++) {
if (!verify_char(*c, flags))
return false;
if (*c == '/') {
if (!verify_component(repo, start, (c - start), flags))
return false;
start = c+1;
}
}
return verify_component(repo, start, (c - start), flags);
}
......@@ -462,4 +462,42 @@ extern bool git_path_does_fs_decompose_unicode(const char *root);
extern bool git_path_is_local_file_url(const char *file_url);
extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path);
/* Flags to determine path validity in `git_path_isvalid` */
#define GIT_PATH_REJECT_TRAVERSAL (1 << 0)
#define GIT_PATH_REJECT_DOT_GIT (1 << 1)
#define GIT_PATH_REJECT_BACKSLASH (1 << 2)
#define GIT_PATH_REJECT_TRAILING_DOT (1 << 3)
#define GIT_PATH_REJECT_TRAILING_SPACE (1 << 4)
#define GIT_PATH_REJECT_TRAILING_COLON (1 << 5)
#define GIT_PATH_REJECT_DOS_GIT_SHORTNAME (1 << 6)
#define GIT_PATH_REJECT_DOS_PATHS (1 << 7)
#define GIT_PATH_REJECT_NT_CHARS (1 << 8)
#ifdef GIT_WIN32
# define GIT_PATH_REJECT_DEFAULTS \
GIT_PATH_REJECT_TRAVERSAL | \
GIT_PATH_REJECT_BACKSLASH | \
GIT_PATH_REJECT_TRAILING_DOT | \
GIT_PATH_REJECT_TRAILING_SPACE | \
GIT_PATH_REJECT_TRAILING_COLON | \
GIT_PATH_REJECT_DOS_GIT_SHORTNAME | \
GIT_PATH_REJECT_DOS_PATHS | \
GIT_PATH_REJECT_NT_CHARS
#else
# define GIT_PATH_REJECT_DEFAULTS GIT_PATH_REJECT_TRAVERSAL
#endif
/*
* Determine whether a path is a valid git path or not - this must not contain
* a '.' or '..' component, or a component that is ".git" (in any case).
*
* `repo` is optional. If specified, it will be used to determine the short
* path name to reject (if `GIT_PATH_REJECT_DOS_SHORTNAME` is specified),
* in addition to the default of "git~1".
*/
extern bool git_path_isvalid(
git_repository *repo,
const char *path,
unsigned int flags);
#endif
......@@ -712,6 +712,11 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *
assert(file && backend && name);
if (!git_path_isvalid(backend->repo, name, GIT_PATH_REJECT_DEFAULTS)) {
giterr_set(GITERR_INVALID, "Invalid reference name '%s'.", name);
return GIT_EINVALIDSPEC;
}
/* Remove a possibly existing empty directory hierarchy
* which name would collide with the reference name
*/
......@@ -1653,6 +1658,11 @@ static int lock_reflog(git_filebuf *file, refdb_fs_backend *backend, const char
repo = backend->repo;
if (!git_path_isvalid(backend->repo, refname, GIT_PATH_REJECT_DEFAULTS)) {
giterr_set(GITERR_INVALID, "Invalid reference name '%s'.", refname);
return GIT_EINVALIDSPEC;
}
if (retrieve_reflog_path(&log_path, repo, refname) < 0)
return -1;
......
......@@ -37,6 +37,9 @@
#define GIT_REPO_VERSION 0
const char *git_repository__8dot3_default = "GIT~1";
size_t git_repository__8dot3_default_len = 5;
static void set_odb(git_repository *repo, git_odb *odb)
{
if (odb) {
......@@ -120,6 +123,7 @@ void git_repository_free(git_repository *repo)
git__free(repo->path_repository);
git__free(repo->workdir);
git__free(repo->namespace);
git__free(repo->name_8dot3);
git__memzero(repo, sizeof(*repo));
git__free(repo);
......@@ -791,6 +795,27 @@ const char *git_repository_get_namespace(git_repository *repo)
return repo->namespace;
}
const char *git_repository__8dot3_name(git_repository *repo)
{
if (!repo->has_8dot3) {
repo->has_8dot3 = 1;
#ifdef GIT_WIN32
if (!repo->is_bare) {
repo->name_8dot3 = git_win32_path_8dot3_name(repo->path_repository);
/* We anticipate the 8.3 name is "GIT~1", so use a static for
* easy testing in the common case */
if (strcasecmp(repo->name_8dot3, git_repository__8dot3_default) == 0)
repo->has_8dot3_default = 1;
}
#endif
}
return repo->has_8dot3_default ?
git_repository__8dot3_default : repo->name_8dot3;
}
static int check_repositoryformatversion(git_config *config)
{
int version;
......
......@@ -120,8 +120,11 @@ struct git_repository {
char *path_repository;
char *workdir;
char *namespace;
char *name_8dot3;
unsigned is_bare:1;
unsigned is_bare:1,
has_8dot3:1,
has_8dot3_default:1;
unsigned int lru_counter;
git_cvar_value cvar_cache[GIT_CVAR_CACHE_MAX];
......@@ -174,4 +177,19 @@ int git_repository__set_orig_head(git_repository *repo, const git_oid *orig_head
int git_repository__cleanup_files(git_repository *repo, const char *files[], size_t files_len);
/*
* Gets the DOS-compatible 8.3 "short name". This will return only the
* short name for the repository directory (ie, "git~1" for ".git"). This
* will always return a pointer to `git_repository__8dot3_default` when
* "GIT~1" is the short name. This will return NULL for bare repositories,
* and systems that do not have a short name.
*/
const char *git_repository__8dot3_name(git_repository *repo);
/* The default DOS-compatible 8.3 "short name" for a git repository,
* "GIT~1".
*/
extern const char *git_repository__8dot3_default;
extern size_t git_repository__8dot3_default_len;
#endif
......@@ -250,6 +250,21 @@ int git__prefixcmp_icase(const char *str, const char *prefix)
return strncasecmp(str, prefix, strlen(prefix));
}
int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix)
{
int s, p;
while(str_n--) {
s = (unsigned char)tolower(*str++);
p = (unsigned char)tolower(*prefix++);
if (s != p)
return s - p;
}
return (0 - *prefix);
}
int git__suffixcmp(const char *str, const char *suffix)
{
size_t a = strlen(str);
......
......@@ -106,6 +106,7 @@ GIT_INLINE(void) git__free(void *ptr)
extern int git__prefixcmp(const char *str, const char *prefix);
extern int git__prefixcmp_icase(const char *str, const char *prefix);
extern int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix);
extern int git__suffixcmp(const char *str, const char *suffix);
GIT_INLINE(int) git__signum(int val)
......
......@@ -267,3 +267,39 @@ int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src)
return len;
}
char *git_win32_path_8dot3_name(const char *path)
{
git_win32_path longpath, shortpath;
wchar_t *start;
char *shortname;
int len, namelen = 1;
if (git_win32_path_from_utf8(longpath, path) < 0)
return NULL;
len = GetShortPathNameW(longpath, shortpath, GIT_WIN_PATH_UTF16);
while (len && shortpath[len-1] == L'\\')
shortpath[--len] = L'\0';
if (len == 0 || len >= GIT_WIN_PATH_UTF16)
return NULL;
for (start = shortpath + (len - 1);
start > shortpath && *(start-1) != '/' && *(start-1) != '\\';
start--)
namelen++;
/* We may not have actually been given a short name. But if we have,
* it will be in the ASCII byte range, so we don't need to worry about
* multi-byte sequences and can allocate naively.
*/
if (namelen > 12 || (shortname = git__malloc(namelen + 1)) == NULL)
return NULL;
if ((len = git__utf16_to_8(shortname, namelen + 1, start)) < 0)
return NULL;
return shortname;
}
......@@ -26,6 +26,11 @@
*/
#define GIT_WIN_PATH_UTF8 (259 * 3 + 1)
/*
* The length of a Windows "shortname", for 8.3 compatibility.
*/
#define GIT_WIN_PATH_SHORTNAME 13
/* Win32 path types */
typedef wchar_t git_win32_path[GIT_WIN_PATH_UTF16];
typedef char git_win32_utf8_path[GIT_WIN_PATH_UTF8];
......@@ -62,4 +67,14 @@ extern int git_win32_path_canonicalize(git_win32_path path);
*/
extern int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src);
/**
* Get the short name for the terminal path component in the given path.
* For example, given "C:\Foo\Bar\Asdf.txt", this will return the short name
* for the file "Asdf.txt".
*
* @param path The given path in UTF-8
* @return The name of the shortname for the given path
*/
extern char *git_win32_path_8dot3_name(const char *path);
#endif
#include "clar_libgit2.h"
#include "checkout_helpers.h"
#include "git2/checkout.h"
#include "repository.h"
#include "buffer.h"
#include "fileops.h"
static const char *repo_name = "nasty";
static git_repository *repo;
static git_checkout_options checkout_opts;
void test_checkout_nasty__initialize(void)
{
repo = cl_git_sandbox_init(repo_name);
GIT_INIT_STRUCTURE(&checkout_opts, GIT_CHECKOUT_OPTIONS_VERSION);
checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE;
}
void test_checkout_nasty__cleanup(void)
{
cl_git_sandbox_cleanup();
}
void test_checkout_fails(const char *refname, const char *filename)
{
git_oid commit_id;
git_commit *commit;
git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
git_buf path = GIT_BUF_INIT;
cl_git_pass(git_buf_joinpath(&path, repo_name, filename));
cl_git_pass(git_reference_name_to_id(&commit_id, repo, refname));
cl_git_pass(git_commit_lookup(&commit, repo, &commit_id));
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
cl_git_fail(git_checkout_tree(repo, (const git_object *)commit, &opts));
cl_assert(!git_path_exists(path.ptr));
git_commit_free(commit);
git_buf_free(&path);
}
/* A tree that contains ".git" as a tree, with a blob inside
* (".git/foobar").
*/
void test_checkout_nasty__dotgit_tree(void)
{
test_checkout_fails("refs/heads/dotgit_tree", ".git/foobar");
}
/* A tree that contains ".GIT" as a tree, with a blob inside
* (".GIT/foobar").
*/
void test_checkout_nasty__dotcapitalgit_tree(void)
{
test_checkout_fails("refs/heads/dotcapitalgit_tree", ".GIT/foobar");
}
/* A tree that contains a tree ".", with a blob inside ("./foobar").
*/
void test_checkout_nasty__dot_tree(void)
{
test_checkout_fails("refs/heads/dot_tree", "foobar");
}
/* A tree that contains a tree ".", with a tree ".git", with a blob
* inside ("./.git/foobar").
*/
void test_checkout_nasty__dot_dotgit_tree(void)
{
test_checkout_fails("refs/heads/dot_dotgit_tree", ".git/foobar");
}
/* A tree that contains a tree, with a tree "..", with a tree ".git", with a
* blob inside ("foo/../.git/foobar").
*/
void test_checkout_nasty__dotdot_dotgit_tree(void)
{
test_checkout_fails("refs/heads/dotdot_dotgit_tree", ".git/foobar");
}
/* A tree that contains a tree, with a tree "..", with a blob inside
* ("foo/../foobar").
*/
void test_checkout_nasty__dotdot_tree(void)
{
test_checkout_fails("refs/heads/dotdot_tree", "foobar");
}
/* A tree that contains a blob with the rogue name ".git/foobar" */
void test_checkout_nasty__dotgit_path(void)
{
test_checkout_fails("refs/heads/dotgit_path", ".git/foobar");
}
/* A tree that contains a blob with the rogue name ".GIT/foobar" */
void test_checkout_nasty__dotcapitalgit_path(void)
{
test_checkout_fails("refs/heads/dotcapitalgit_path", ".GIT/foobar");
}
/* A tree that contains a blob with the rogue name "./.git/foobar" */
void test_checkout_nasty__dot_dotgit_path(void)
{
test_checkout_fails("refs/heads/dot_dotgit_path", ".git/foobar");
}
/* A tree that contains a blob with the rogue name "./.GIT/foobar" */
void test_checkout_nasty__dot_dotcapitalgit_path(void)
{
test_checkout_fails("refs/heads/dot_dotcapitalgit_path", ".GIT/foobar");
}
/* A tree that contains a blob with the rogue name "foo/../.git/foobar" */
void test_checkout_nasty__dotdot_dotgit_path(void)
{
test_checkout_fails("refs/heads/dotdot_dotgit_path", ".git/foobar");
}
/* A tree that contains a blob with the rogue name "foo/../.GIT/foobar" */
void test_checkout_nasty__dotdot_dotcapitalgit_path(void)
{
test_checkout_fails("refs/heads/dotdot_dotcapitalgit_path", ".GIT/foobar");
}
/* A tree that contains a blob with the rogue name "foo/." */
void test_checkout_nasty__dot_path(void)
{
test_checkout_fails("refs/heads/dot_path", "./foobar");
}
/* A tree that contains a blob with the rogue name "foo/." */
void test_checkout_nasty__dot_path_two(void)
{
test_checkout_fails("refs/heads/dot_path_two", "foo/.");
}
/* A tree that contains a blob with the rogue name "foo/../foobar" */
void test_checkout_nasty__dotdot_path(void)
{
test_checkout_fails("refs/heads/dotdot_path", "foobar");
}
/* A tree that contains an entry with a backslash ".git\foobar" */
void test_checkout_nasty__dotgit_backslash_path(void)
{
#ifdef GIT_WIN32
test_checkout_fails("refs/heads/dotgit_backslash_path", ".git/foobar");
#endif
}
/* A tree that contains an entry with a backslash ".GIT\foobar" */
void test_checkout_nasty__dotcapitalgit_backslash_path(void)
{
#ifdef GIT_WIN32
test_checkout_fails("refs/heads/dotcapitalgit_backslash_path", ".GIT/foobar");
#endif
}
/* A tree that contains an entry with a backslash ".\.GIT\foobar" */
void test_checkout_nasty__dot_backslash_dotcapitalgit_path(void)
{
#ifdef GIT_WIN32
test_checkout_fails("refs/heads/dot_backslash_dotcapitalgit_path", ".GIT/foobar");
#endif
}
/* A tree that contains an entry ".git.", because Win32 APIs will drop the
* trailing slash.
*/
void test_checkout_nasty__dot_git_dot(void)
{
#ifdef GIT_WIN32
test_checkout_fails("refs/heads/dot_git_dot", ".git/foobar");
#endif
}
/* A tree that contains an entry "git~1", because that is typically the
* short name for ".git".
*/
void test_checkout_nasty__git_tilde1(void)
{
#ifdef GIT_WIN32
test_checkout_fails("refs/heads/git_tilde1", ".git/foobar");
#endif
}
/* A tree that contains an entry "git~2", when we have forced the short
* name for ".git" into "GIT~2".
*/
void test_checkout_nasty__git_custom_shortname(void)
{
#ifdef GIT_WIN32
cl_must_pass(p_rename("nasty/.git", "nasty/_temp"));
cl_git_write2file("nasty/git~1", "", 0, O_RDWR|O_CREAT, 0666);
cl_must_pass(p_rename("nasty/_temp", "nasty/.git"));
test_checkout_fails("refs/heads/git_tilde2", ".git/foobar");
#endif
}
/* A tree that contains an entry "git~3", which should be allowed, since
* it is not the typical short name ("GIT~1") or the actual short name
* ("GIT~2") for ".git".
*/
void test_checkout_nasty__only_looks_like_a_git_shortname(void)
{
#ifdef GIT_WIN32
git_oid commit_id;
git_commit *commit;
git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
cl_must_pass(p_rename("nasty/.git", "nasty/_temp"));
cl_git_write2file("nasty/git~1", "", 0, O_RDWR|O_CREAT, 0666);
cl_must_pass(p_rename("nasty/_temp", "nasty/.git"));
cl_git_pass(git_reference_name_to_id(&commit_id, repo, "refs/heads/git_tilde3"));
cl_git_pass(git_commit_lookup(&commit, repo, &commit_id));
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
cl_git_pass(git_checkout_tree(repo, (const git_object *)commit, &opts));
cl_assert(git_path_exists("nasty/git~3/foobar"));
git_commit_free(commit);
#endif
}
/* A tree that contains an entry "git:", because Win32 APIs will reject
* that as looking too similar to a drive letter.
*/
void test_checkout_nasty__dot_git_colon(void)
{
#ifdef GIT_WIN32
test_checkout_fails("refs/heads/dot_git_colon", ".git/foobar");
#endif
}
/* A tree that contains an entry "git:foo", because Win32 APIs will turn
* that into ".git".
*/
void test_checkout_nasty__dot_git_colon_stuff(void)
{
#ifdef GIT_WIN32
test_checkout_fails("refs/heads/dot_git_colon_stuff", ".git/foobar");
#endif
}
......@@ -51,3 +51,197 @@ void test_path_core__make_relative(void)
test_make_relative("/path", "/path", "pathtofoo", GIT_ENOTFOUND);
test_make_relative("path", "path", "pathtofoo", GIT_ENOTFOUND);
}
void test_path_core__isvalid_standard(void)
{
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/bar", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/bar/file.txt", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/bar/.file", 0));
}
void test_path_core__isvalid_empty_dir_component(void)
{
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo//bar", 0));
/* leading slash */
cl_assert_equal_b(false, git_path_isvalid(NULL, "/", 0));
cl_assert_equal_b(false, git_path_isvalid(NULL, "/foo", 0));
cl_assert_equal_b(false, git_path_isvalid(NULL, "/foo/bar", 0));
/* trailing slash */
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/", 0));
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/bar/", 0));
}
void test_path_core__isvalid_dot_and_dotdot(void)
{
cl_assert_equal_b(true, git_path_isvalid(NULL, ".", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "./foo", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/.", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "./foo", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "..", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "../foo", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/..", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "../foo", 0));
cl_assert_equal_b(false, git_path_isvalid(NULL, ".", GIT_PATH_REJECT_TRAVERSAL));
cl_assert_equal_b(false, git_path_isvalid(NULL, "./foo", GIT_PATH_REJECT_TRAVERSAL));
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.", GIT_PATH_REJECT_TRAVERSAL));
cl_assert_equal_b(false, git_path_isvalid(NULL, "./foo", GIT_PATH_REJECT_TRAVERSAL));
cl_assert_equal_b(false, git_path_isvalid(NULL, "..", GIT_PATH_REJECT_TRAVERSAL));
cl_assert_equal_b(false, git_path_isvalid(NULL, "../foo", GIT_PATH_REJECT_TRAVERSAL));
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/..", GIT_PATH_REJECT_TRAVERSAL));
cl_assert_equal_b(false, git_path_isvalid(NULL, "../foo", GIT_PATH_REJECT_TRAVERSAL));
}
void test_path_core__isvalid_dot_git(void)
{
cl_assert_equal_b(true, git_path_isvalid(NULL, ".git", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, ".git/foo", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/.git", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/.git/bar", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/.GIT/bar", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/bar/.Git", 0));
cl_assert_equal_b(false, git_path_isvalid(NULL, ".git", GIT_PATH_REJECT_DOT_GIT));
cl_assert_equal_b(false, git_path_isvalid(NULL, ".git/foo", GIT_PATH_REJECT_DOT_GIT));
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.git", GIT_PATH_REJECT_DOT_GIT));
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.git/bar", GIT_PATH_REJECT_DOT_GIT));
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.GIT/bar", GIT_PATH_REJECT_DOT_GIT));
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/bar/.Git", GIT_PATH_REJECT_DOT_GIT));
cl_assert_equal_b(true, git_path_isvalid(NULL, "!git", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/!git", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "!git/bar", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, ".tig", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/.tig", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, ".tig/bar", 0));
}
void test_path_core__isvalid_backslash(void)
{
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo\\file.txt", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/bar\\file.txt", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/bar\\", 0));
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo\\file.txt", GIT_PATH_REJECT_BACKSLASH));
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/bar\\file.txt", GIT_PATH_REJECT_BACKSLASH));
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/bar\\", GIT_PATH_REJECT_BACKSLASH));
}
void test_path_core__isvalid_trailing_dot(void)
{
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo.", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo...", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/bar.", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo./bar", 0));
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo.", GIT_PATH_REJECT_TRAILING_DOT));
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo...", GIT_PATH_REJECT_TRAILING_DOT));
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/bar.", GIT_PATH_REJECT_TRAILING_DOT));
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo./bar", GIT_PATH_REJECT_TRAILING_DOT));
}
void test_path_core__isvalid_trailing_space(void)
{
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo ", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo ", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/bar ", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, " ", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo /bar", 0));
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo ", GIT_PATH_REJECT_TRAILING_SPACE));
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo ", GIT_PATH_REJECT_TRAILING_SPACE));
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/bar ", GIT_PATH_REJECT_TRAILING_SPACE));
cl_assert_equal_b(false, git_path_isvalid(NULL, " ", GIT_PATH_REJECT_TRAILING_SPACE));
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo /bar", GIT_PATH_REJECT_TRAILING_SPACE));
}
void test_path_core__isvalid_trailing_colon(void)
{
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo:", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/bar:", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, ":", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo:/bar", 0));
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo:", GIT_PATH_REJECT_TRAILING_COLON));
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/bar:", GIT_PATH_REJECT_TRAILING_COLON));
cl_assert_equal_b(false, git_path_isvalid(NULL, ":", GIT_PATH_REJECT_TRAILING_COLON));
cl_assert_equal_b(false, git_path_isvalid(NULL, "foo:/bar", GIT_PATH_REJECT_TRAILING_COLON));
}
void test_path_core__isvalid_dos_git_shortname(void)
{
cl_assert_equal_b(true, git_path_isvalid(NULL, "git~1", 0));
cl_assert_equal_b(false, git_path_isvalid(NULL, "git~1", GIT_PATH_REJECT_DOS_GIT_SHORTNAME));
}
void test_path_core__isvalid_dos_paths(void)
{
cl_assert_equal_b(true, git_path_isvalid(NULL, "aux", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "aux.", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "aux:", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "aux.asdf", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "aux.asdf\\zippy", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "aux:asdf\\foobar", 0));
cl_assert_equal_b(false, git_path_isvalid(NULL, "aux", GIT_PATH_REJECT_DOS_PATHS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "aux.", GIT_PATH_REJECT_DOS_PATHS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "aux:", GIT_PATH_REJECT_DOS_PATHS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "aux.asdf", GIT_PATH_REJECT_DOS_PATHS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "aux.asdf\\zippy", GIT_PATH_REJECT_DOS_PATHS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "aux:asdf\\foobar", GIT_PATH_REJECT_DOS_PATHS));
cl_assert_equal_b(true, git_path_isvalid(NULL, "aux1", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "aux1", GIT_PATH_REJECT_DOS_PATHS));
cl_assert_equal_b(true, git_path_isvalid(NULL, "auxn", GIT_PATH_REJECT_DOS_PATHS));
cl_assert_equal_b(true, git_path_isvalid(NULL, "aux\\foo", GIT_PATH_REJECT_DOS_PATHS));
}
void test_path_core__isvalid_dos_paths_withnum(void)
{
cl_assert_equal_b(true, git_path_isvalid(NULL, "com1", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "com1.", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "com1:", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "com1.asdf", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "com1.asdf\\zippy", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "com1:asdf\\foobar", 0));
cl_assert_equal_b(false, git_path_isvalid(NULL, "com1", GIT_PATH_REJECT_DOS_PATHS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "com1.", GIT_PATH_REJECT_DOS_PATHS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "com1:", GIT_PATH_REJECT_DOS_PATHS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "com1.asdf", GIT_PATH_REJECT_DOS_PATHS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "com1.asdf\\zippy", GIT_PATH_REJECT_DOS_PATHS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "com1:asdf\\foobar", GIT_PATH_REJECT_DOS_PATHS));
cl_assert_equal_b(true, git_path_isvalid(NULL, "com10", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "com10", GIT_PATH_REJECT_DOS_PATHS));
cl_assert_equal_b(true, git_path_isvalid(NULL, "comn", GIT_PATH_REJECT_DOS_PATHS));
cl_assert_equal_b(true, git_path_isvalid(NULL, "com1\\foo", GIT_PATH_REJECT_DOS_PATHS));
}
void test_core_path__isvalid_nt_chars(void)
{
cl_assert_equal_b(true, git_path_isvalid(NULL, "asdf\001foo", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "asdf\037bar", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "asdf<bar", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "asdf>foo", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "asdf:foo", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "asdf\"bar", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "asdf|foo", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "asdf?bar", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "asdf*bar", 0));
cl_assert_equal_b(false, git_path_isvalid(NULL, "asdf\001foo", GIT_PATH_REJECT_NT_CHARS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "asdf\037bar", GIT_PATH_REJECT_NT_CHARS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "asdf<bar", GIT_PATH_REJECT_NT_CHARS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "asdf>foo", GIT_PATH_REJECT_NT_CHARS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "asdf:foo", GIT_PATH_REJECT_NT_CHARS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "asdf\"bar", GIT_PATH_REJECT_NT_CHARS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "asdf|foo", GIT_PATH_REJECT_NT_CHARS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "asdf?bar", GIT_PATH_REJECT_NT_CHARS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "asdf*bar", GIT_PATH_REJECT_NT_CHARS));
}
......@@ -188,3 +188,27 @@ void test_path_win32__canonicalize(void)
test_canonicalize(L"\\\\server\\..\\..\\share\\.\\foo", L"\\\\server\\share\\foo");
#endif
}
void test_path_win32__8dot3_name(void)
{
#ifdef GIT_WIN32
char *shortname;
/* Some guaranteed short names */
cl_assert_equal_s("PROGRA~1", (shortname = git_win32_path_8dot3_name("C:\\Program Files")));
git__free(shortname);
cl_assert_equal_s("WINDOWS", (shortname = git_win32_path_8dot3_name("C:\\WINDOWS")));
git__free(shortname);
/* Create some predictible short names */
cl_must_pass(p_mkdir(".foo", 0777));
cl_assert_equal_s("FOO~1", (shortname = git_win32_path_8dot3_name(".foo")));
git__free(shortname);
cl_git_write2file("bar~1", "foobar\n", 7, O_RDWR|O_CREAT, 0666);
cl_must_pass(p_mkdir(".bar", 0777));
cl_assert_equal_s("BAR~2", (shortname = git_win32_path_8dot3_name(".bar")));
git__free(shortname);
#endif
}
4aa347c8bb0456230f43f34833c97b9f52c40f62
8bcbb6e0c0f9554efd5401e1ec14a4b2595eb3bf
4414ac920acabc3eb00e3cf9375eeb0cb6859c15
26b665c162f67acae67779445f3c7b9782b0a6d7
bf7ab4723fcc57ecc7fceccf591d6c4773491569
debdc4a004fda6141a17d9c297617be70d40248f
697dc3d723a018538eb819d5db2035c15109af73
91602c85bb50dd834205edd30435b77d5bb9ccf0
8f1dcd43aa0164eb6ec319c3ec8879ca5cf62c1e
5341a7b545d71198b076b8ba3374a75c9a290640
6594bdbad86bbc8d3ed0806a23827203fbab56c6
94f37c29173c8fa45a232b17e745c82132b2fafd
899ff28744bed5bece69c78ba752c7dc3e954629
fa9cfdbeaaf3a91ff4b84d74412cd59d9b16a615
e399c4fc4c07cb7947d2f3d966bc374df6ccc691
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