Commit 247b3f4e by Vicent Marti

Merge remote-tracking branch 'origin/master_patch'

parents 4eb97ef3 dce7b1a4
......@@ -136,3 +136,7 @@ v0.21 + 1
* git_libgit2_init() and git_libgit2_shutdown() now return the number of
initializations of the library, so consumers may schedule work on the
first initialization.
* git_treebuilder_create now takes a repository so that it can query
repository configuration. Subsequently, git_treebuilder_write no
longer takes a repository.
......@@ -247,11 +247,12 @@ GIT_EXTERN(int) git_tree_entry_to_object(
* entries and will have to be filled manually.
*
* @param out Pointer where to store the tree builder
* @param repo Repository in which to store the object
* @param source Source tree to initialize the builder (optional)
* @return 0 on success; error code otherwise
*/
GIT_EXTERN(int) git_treebuilder_create(
git_treebuilder **out, const git_tree *source);
git_treebuilder **out, git_repository *repo, const git_tree *source);
/**
* Clear all the entires in the builder
......@@ -368,12 +369,11 @@ GIT_EXTERN(void) git_treebuilder_filter(
* identifying SHA1 hash will be stored in the `id` pointer.
*
* @param id Pointer to store the OID of the newly written tree
* @param repo Repository in which to store the object
* @param bld Tree builder to write
* @return 0 or an error code
*/
GIT_EXTERN(int) git_treebuilder_write(
git_oid *id, git_repository *repo, git_treebuilder *bld);
git_oid *id, git_treebuilder *bld);
/** Callback for the tree traversal method */
......
......@@ -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;
......
......@@ -76,6 +76,8 @@ static struct map_data _cvar_maps[] = {
{"core.precomposeunicode", NULL, 0, GIT_PRECOMPOSE_DEFAULT },
{"core.safecrlf", _cvar_map_safecrlf, ARRAY_SIZE(_cvar_map_safecrlf), GIT_SAFE_CRLF_DEFAULT},
{"core.logallrefupdates", NULL, 0, GIT_LOGALLREFUPDATES_DEFAULT },
{"core.protecthfs", NULL, 0, GIT_PROTECTHFS_DEFAULT },
{"core.protectntfs", NULL, 0, GIT_PROTECTNTFS_DEFAULT },
};
int git_config__cvar(int *out, git_config *config, git_cvar_cached cvar)
......
......@@ -762,23 +762,35 @@ void git_index_entry__init_from_stat(
entry->file_size = st->st_size;
}
static git_index_entry *index_entry_alloc(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 =
git__calloc(sizeof(struct entry_internal) + pathlen + 1, 1);
if (!entry)
return NULL;
struct entry_internal *entry;
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);
entry->pathlen = pathlen;
memcpy(entry->path, path, pathlen);
entry->entry.path = entry->path;
return (git_index_entry *)entry;
*out = (git_index_entry *)entry;
return 0;
}
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;
......@@ -790,14 +802,17 @@ static int index_entry_init(
"Could not initialize index entry. "
"Index is not backed up by an existing repository.");
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 */
error = git_blob__create_from_paths(
&oid, &st, INDEX_OWNER(index), NULL, rel_path, 0, true);
if (error < 0)
return error;
entry = index_entry_alloc(rel_path);
GITERR_CHECK_ALLOC(entry);
if (error < 0) {
index_entry_free(entry);
return error;
}
entry->id = oid;
git_index_entry__init_from_stat(entry, &st, !index->distrust_filemode);
......@@ -853,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;
......@@ -862,11 +880,11 @@ static int index_entry_dup(git_index_entry **out, const git_index_entry *src)
return 0;
}
*out = entry = index_entry_alloc(src->path);
GITERR_CHECK_ALLOC(entry);
if (index_entry_create(&entry, repo, src->path) < 0)
return -1;
index_entry_cpy(entry, src);
*out = entry;
return 0;
}
......@@ -1131,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;
......@@ -1251,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++) {
......@@ -1770,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;
......@@ -1834,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;
......@@ -1935,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) {
......@@ -2296,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;
......@@ -2316,8 +2338,8 @@ static int read_tree_cb(
if (git_buf_joinpath(&path, root, tentry->filename) < 0)
return -1;
entry = index_entry_alloc(path.ptr);
GITERR_CHECK_ALLOC(entry);
if (index_entry_create(&entry, INDEX_OWNER(data->index), path.ptr) < 0)
return -1;
entry->mode = tentry->attr;
entry->id = tentry->oid;
......@@ -2357,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;
......@@ -2476,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;
......
......@@ -107,7 +107,7 @@ static int tree_write(
const git_tree_entry *entry;
git_oid tree_oid;
if ((error = git_treebuilder_create(&tb, source_tree)) < 0)
if ((error = git_treebuilder_create(&tb, repo, source_tree)) < 0)
goto cleanup;
if (object_oid) {
......@@ -119,7 +119,7 @@ static int tree_write(
goto cleanup;
}
if ((error = git_treebuilder_write(&tree_oid, repo, tb)) < 0)
if ((error = git_treebuilder_write(&tree_oid, tb)) < 0)
goto cleanup;
error = git_tree_lookup(out, repo, &tree_oid);
......
......@@ -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,258 @@ 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);
}
/* 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] != ':');
}
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);
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 tolower(codepoint);
}
return 0; /* NULL byte -- end of string */
}
static bool verify_dotgit_hfs(const char *path, size_t 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)
return true;
return false;
}
GIT_INLINE(bool) verify_dotgit_ntfs(git_repository *repo, const char *path, size_t len)
{
const char *shortname = NULL;
size_t i, start, shortname_len = 0;
/* See if the repo has a custom shortname (not "GIT~1") */
if (repo &&
(shortname = git_repository__8dot3_name(repo)) &&
shortname != git_repository__8dot3_default)
shortname_len = strlen(shortname);
if (len >= 4 && strncasecmp(path, ".git", 4) == 0)
start = 4;
else if (len >= git_repository__8dot3_default_len &&
strncasecmp(path, git_repository__8dot3_default, git_repository__8dot3_default_len) == 0)
start = git_repository__8dot3_default_len;
else if (shortname_len && len >= shortname_len &&
strncasecmp(path, shortname, shortname_len) == 0)
start = shortname_len;
else
return true;
/* Reject paths beginning with ".git\" */
if (path[start] == '\\')
return false;
for (i = start; i < len; i++) {
if (path[i] != ' ' && path[i] != '.')
return true;
}
return false;
}
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;
}
/*
* 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_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 &&
!verify_dotgit_hfs(component, len))
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_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;
return true;
}
GIT_INLINE(unsigned int) dotgit_flags(
git_repository *repo,
unsigned int flags)
{
int protectHFS = 0, protectNTFS = 0;
#ifdef __APPLE__
protectHFS = 1;
#endif
#ifdef GIT_WIN32
protectNTFS = 1;
#endif
if (repo && !protectHFS)
git_repository__cvar(&protectHFS, repo, GIT_CVAR_PROTECTHFS);
if (protectHFS)
flags |= GIT_PATH_REJECT_DOT_GIT_HFS;
if (repo && !protectNTFS)
git_repository__cvar(&protectNTFS, repo, GIT_CVAR_PROTECTNTFS);
if (protectNTFS)
flags |= GIT_PATH_REJECT_DOT_GIT_NTFS;
return flags;
}
bool git_path_isvalid(
git_repository *repo,
const char *path,
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), flags))
return false;
start = c+1;
}
}
return verify_component(repo, start, (c - start), flags);
}
......@@ -462,4 +462,47 @@ 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_SLASH (1 << 2)
#define GIT_PATH_REJECT_BACKSLASH (1 << 3)
#define GIT_PATH_REJECT_TRAILING_DOT (1 << 4)
#define GIT_PATH_REJECT_TRAILING_SPACE (1 << 5)
#define GIT_PATH_REJECT_TRAILING_COLON (1 << 6)
#define GIT_PATH_REJECT_DOS_PATHS (1 << 7)
#define GIT_PATH_REJECT_NT_CHARS (1 << 8)
#define GIT_PATH_REJECT_DOT_GIT_HFS (1 << 9)
#define GIT_PATH_REJECT_DOT_GIT_NTFS (1 << 10)
/* Default path safety for writing files to disk: since we use the
* Win32 "File Namespace" APIs ("\\?\") we need to protect from
* paths that the normal Win32 APIs would not write.
*/
#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_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
......@@ -707,11 +707,16 @@ static int reference_path_available(
static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *name)
{
int error;
int error;
git_buf ref_path = GIT_BUF_INIT;
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;
......
......@@ -40,6 +40,8 @@ typedef enum {
GIT_CVAR_PRECOMPOSE, /* core.precomposeunicode */
GIT_CVAR_SAFE_CRLF, /* core.safecrlf */
GIT_CVAR_LOGALLREFUPDATES, /* core.logallrefupdates */
GIT_CVAR_PROTECTHFS, /* core.protectHFS */
GIT_CVAR_PROTECTNTFS, /* core.protectNTFS */
GIT_CVAR_CACHE_MAX
} git_cvar_cached;
......@@ -96,6 +98,10 @@ typedef enum {
/* core.logallrefupdates */
GIT_LOGALLREFUPDATES_UNSET = 2,
GIT_LOGALLREFUPDATES_DEFAULT = GIT_LOGALLREFUPDATES_UNSET,
/* core.protectHFS */
GIT_PROTECTHFS_DEFAULT = GIT_CVAR_FALSE,
/* core.protectNTFS */
GIT_PROTECTNTFS_DEFAULT = GIT_CVAR_FALSE,
} git_cvar_value;
/* internal repository init flags */
......@@ -120,8 +126,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 +183,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
......@@ -50,14 +50,11 @@ GIT_INLINE(git_filemode_t) normalize_filemode(git_filemode_t filemode)
return GIT_FILEMODE_BLOB;
}
static int valid_entry_name(const char *filename)
static int valid_entry_name(git_repository *repo, const char *filename)
{
return *filename != '\0' &&
strchr(filename, '/') == NULL &&
(*filename != '.' ||
(strcmp(filename, ".") != 0 &&
strcmp(filename, "..") != 0 &&
strcmp(filename, DOT_GIT) != 0));
git_path_isvalid(repo, filename,
GIT_PATH_REJECT_TRAVERSAL | GIT_PATH_REJECT_DOT_GIT | GIT_PATH_REJECT_SLASH);
}
static int entry_sort_cmp(const void *a, const void *b)
......@@ -455,7 +452,7 @@ static int append_entry(
git_tree_entry *entry;
int error = 0;
if (!valid_entry_name(filename))
if (!valid_entry_name(bld->repo, filename))
return tree_error("Failed to insert entry. Invalid name for a tree entry", filename);
entry = alloc_entry(filename);
......@@ -493,7 +490,7 @@ static int write_tree(
return (int)find_next_dir(dirname, index, start);
}
if ((error = git_treebuilder_create(&bld, NULL)) < 0 || bld == NULL)
if ((error = git_treebuilder_create(&bld, repo, NULL)) < 0 || bld == NULL)
return -1;
/*
......@@ -564,7 +561,7 @@ static int write_tree(
}
}
if (git_treebuilder_write(oid, repo, bld) < 0)
if (git_treebuilder_write(oid, bld) < 0)
goto on_error;
git_treebuilder_free(bld);
......@@ -627,16 +624,21 @@ int git_tree__write_index(
return ret;
}
int git_treebuilder_create(git_treebuilder **builder_p, const git_tree *source)
int git_treebuilder_create(
git_treebuilder **builder_p,
git_repository *repo,
const git_tree *source)
{
git_treebuilder *bld;
size_t i;
assert(builder_p);
assert(builder_p && repo);
bld = git__calloc(1, sizeof(git_treebuilder));
GITERR_CHECK_ALLOC(bld);
bld->repo = repo;
if (git_strmap_alloc(&bld->map) < 0) {
git__free(bld);
return -1;
......@@ -678,7 +680,7 @@ int git_treebuilder_insert(
if (!valid_filemode(filemode))
return tree_error("Failed to insert entry. Invalid filemode for file", filename);
if (!valid_entry_name(filename))
if (!valid_entry_name(bld->repo, filename))
return tree_error("Failed to insert entry. Invalid name for a tree entry", filename);
pos = git_strmap_lookup_index(bld->map, filename);
......@@ -738,7 +740,7 @@ int git_treebuilder_remove(git_treebuilder *bld, const char *filename)
return 0;
}
int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *bld)
int git_treebuilder_write(git_oid *oid, git_treebuilder *bld)
{
int error = 0;
size_t i, entrycount;
......@@ -777,7 +779,7 @@ int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *b
git_vector_free(&entries);
if (!error &&
!(error = git_repository_odb__weakptr(&odb, repo)))
!(error = git_repository_odb__weakptr(&odb, bld->repo)))
error = git_odb_write(oid, odb, tree.ptr, tree.size, GIT_OBJ_TREE);
git_buf_free(&tree);
......
......@@ -26,6 +26,7 @@ struct git_tree {
};
struct git_treebuilder {
git_repository *repo;
git_strmap *map;
};
......
......@@ -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);
......@@ -649,3 +664,79 @@ void git__insertsort_r(
if (freeswap)
git__free(swapel);
}
static const int8_t utf8proc_utf8class[256] = {
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0
};
int git__utf8_charlen(const uint8_t *str, int str_len)
{
int length, i;
length = utf8proc_utf8class[str[0]];
if (!length)
return -1;
if (str_len >= 0 && length > str_len)
return -str_len;
for (i = 1; i < length; i++) {
if ((str[i] & 0xC0) != 0x80)
return -i;
}
return length;
}
int git__utf8_iterate(const uint8_t *str, int str_len, int32_t *dst)
{
int length;
int32_t uc = -1;
*dst = -1;
length = git__utf8_charlen(str, str_len);
if (length < 0)
return -1;
switch (length) {
case 1:
uc = str[0];
break;
case 2:
uc = ((str[0] & 0x1F) << 6) + (str[1] & 0x3F);
if (uc < 0x80) uc = -1;
break;
case 3:
uc = ((str[0] & 0x0F) << 12) + ((str[1] & 0x3F) << 6)
+ (str[2] & 0x3F);
if (uc < 0x800 || (uc >= 0xD800 && uc < 0xE000) ||
(uc >= 0xFDD0 && uc < 0xFDF0)) uc = -1;
break;
case 4:
uc = ((str[0] & 0x07) << 18) + ((str[1] & 0x3F) << 12)
+ ((str[2] & 0x3F) << 6) + (str[3] & 0x3F);
if (uc < 0x10000 || uc >= 0x110000) uc = -1;
break;
}
if (uc < 0 || ((uc & 0xFFFF) >= 0xFFFE))
return -1;
*dst = uc;
return length;
}
......@@ -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)
......@@ -367,6 +368,17 @@ extern int git__date_rfc2822_fmt(char *out, size_t len, const git_time *date);
extern size_t git__unescape(char *str);
/*
* Iterate through an UTF-8 string, yielding one
* codepoint at a time.
*
* @param str current position in the string
* @param str_len size left in the string; -1 if the string is NULL-terminated
* @param dst pointer where to store the current codepoint
* @return length in bytes of the read codepoint; -1 if the codepoint was invalid
*/
extern int git__utf8_iterate(const uint8_t *str, int str_len, int32_t *dst);
/*
* Safely zero-out memory, making sure that the compiler
* doesn't optimize away the operation.
*/
......
......@@ -5,6 +5,7 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "path_w32.h"
#include "utf-conv.h"
#include "path.h"
#include "findfile.h"
......
/*
* 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 "common.h"
#include "path.h"
#include "path_w32.h"
#include "utf-conv.h"
#define PATH__NT_NAMESPACE L"\\\\?\\"
#define PATH__NT_NAMESPACE_LEN 4
#define PATH__ABSOLUTE_LEN 3
#define path__is_dirsep(p) ((p) == '/' || (p) == '\\')
#define path__is_absolute(p) \
(git__isalpha((p)[0]) && (p)[1] == ':' && ((p)[2] == '\\' || (p)[2] == '/'))
#define path__is_nt_namespace(p) \
(((p)[0] == '\\' && (p)[1] == '\\' && (p)[2] == '?' && (p)[3] == '\\') || \
((p)[0] == '/' && (p)[1] == '/' && (p)[2] == '?' && (p)[3] == '/'))
#define path__is_unc(p) \
(((p)[0] == '\\' && (p)[1] == '\\') || ((p)[0] == '/' && (p)[1] == '/'))
GIT_INLINE(int) path__cwd(wchar_t *path, int size)
{
int len;
if ((len = GetCurrentDirectoryW(size, path)) == 0) {
errno = GetLastError() == ERROR_ACCESS_DENIED ? EACCES : ENOENT;
return -1;
} else if (len > size) {
errno = ENAMETOOLONG;
return -1;
}
/* The Win32 APIs may return "\\?\" once you've used it first.
* But it may not. What a gloriously predictible API!
*/
if (wcsncmp(path, PATH__NT_NAMESPACE, PATH__NT_NAMESPACE_LEN))
return len;
len -= PATH__NT_NAMESPACE_LEN;
memmove(path, path + PATH__NT_NAMESPACE_LEN, sizeof(wchar_t) * len);
return len;
}
static wchar_t *path__skip_server(wchar_t *path)
{
wchar_t *c;
for (c = path; *c; c++) {
if (path__is_dirsep(*c))
return c + 1;
}
return c;
}
static wchar_t *path__skip_prefix(wchar_t *path)
{
if (path__is_nt_namespace(path)) {
path += PATH__NT_NAMESPACE_LEN;
if (wcsncmp(path, L"UNC\\", 4) == 0)
path = path__skip_server(path + 4);
else if (path__is_absolute(path))
path += PATH__ABSOLUTE_LEN;
} else if (path__is_absolute(path)) {
path += PATH__ABSOLUTE_LEN;
} else if (path__is_unc(path)) {
path = path__skip_server(path + 2);
}
return path;
}
int git_win32_path_canonicalize(git_win32_path path)
{
wchar_t *base, *from, *to, *next;
size_t len;
base = to = path__skip_prefix(path);
/* Unposixify if the prefix */
for (from = path; from < to; from++) {
if (*from == L'/')
*from = L'\\';
}
while (*from) {
for (next = from; *next; ++next) {
if (*next == L'/') {
*next = L'\\';
break;
}
if (*next == L'\\')
break;
}
len = next - from;
if (len == 1 && from[0] == L'.')
/* do nothing with singleton dot */;
else if (len == 2 && from[0] == L'.' && from[1] == L'.') {
if (to == base) {
/* no more path segments to strip, eat the "../" */
if (*next == L'\\')
len++;
base = to;
} else {
/* back up a path segment */
while (to > base && to[-1] == L'\\') to--;
while (to > base && to[-1] != L'\\') to--;
}
} else {
if (*next == L'\\' && *from != L'\\')
len++;
if (to != from)
memmove(to, from, sizeof(wchar_t) * len);
to += len;
}
from += len;
while (*from == L'\\') from++;
}
/* Strip trailing backslashes */
while (to > base && to[-1] == L'\\') to--;
*to = L'\0';
return (to - path);
}
int git_win32_path__cwd(wchar_t *out, size_t len)
{
int cwd_len;
if ((cwd_len = path__cwd(out, len)) < 0)
return -1;
/* UNC paths */
if (wcsncmp(L"\\\\", out, 2) == 0) {
/* Our buffer must be at least 5 characters larger than the
* current working directory: we swallow one of the leading
* '\'s, but we we add a 'UNC' specifier to the path, plus
* a trailing directory separator, plus a NUL.
*/
if (cwd_len > MAX_PATH - 4) {
errno = ENAMETOOLONG;
return -1;
}
memmove(out+2, out, sizeof(wchar_t) * cwd_len);
out[0] = L'U';
out[1] = L'N';
out[2] = L'C';
cwd_len += 2;
}
/* Our buffer must be at least 2 characters larger than the current
* working directory. (One character for the directory separator,
* one for the null.
*/
else if (cwd_len > MAX_PATH - 2) {
errno = ENAMETOOLONG;
return -1;
}
return cwd_len;
}
int git_win32_path_from_utf8(git_win32_path out, const char *src)
{
wchar_t *dest = out;
/* All win32 paths are in NT-prefixed format, beginning with "\\?\". */
memcpy(dest, PATH__NT_NAMESPACE, sizeof(wchar_t) * PATH__NT_NAMESPACE_LEN);
dest += PATH__NT_NAMESPACE_LEN;
/* See if this is an absolute path (beginning with a drive letter) */
if (path__is_absolute(src)) {
if (git__utf8_to_16(dest, MAX_PATH, src) < 0)
return -1;
}
/* File-prefixed NT-style paths beginning with \\?\ */
else if (path__is_nt_namespace(src)) {
/* Skip the NT prefix, the destination already contains it */
if (git__utf8_to_16(dest, MAX_PATH, src + PATH__NT_NAMESPACE_LEN) < 0)
return -1;
}
/* UNC paths */
else if (path__is_unc(src)) {
memcpy(dest, L"UNC\\", sizeof(wchar_t) * 4);
dest += 4;
/* Skip the leading "\\" */
if (git__utf8_to_16(dest, MAX_PATH - 2, src + 2) < 0)
return -1;
}
/* Absolute paths omitting the drive letter */
else if (src[0] == '\\' || src[0] == '/') {
if (path__cwd(dest, MAX_PATH) < 0)
return -1;
if (!path__is_absolute(dest)) {
errno = ENOENT;
return -1;
}
/* Skip the drive letter specification ("C:") */
if (git__utf8_to_16(dest + 2, MAX_PATH - 2, src) < 0)
return -1;
}
/* Relative paths */
else {
int cwd_len;
if ((cwd_len = git_win32_path__cwd(dest, MAX_PATH)) < 0)
return -1;
dest[cwd_len++] = L'\\';
if (git__utf8_to_16(dest + cwd_len, MAX_PATH - cwd_len, src) < 0)
return -1;
}
return git_win32_path_canonicalize(out);
}
int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src)
{
char *out = dest;
int len;
/* Strip NT namespacing "\\?\" */
if (path__is_nt_namespace(src)) {
src += 4;
/* "\\?\UNC\server\share" -> "\\server\share" */
if (wcsncmp(src, L"UNC\\", 4) == 0) {
src += 4;
memcpy(dest, "\\\\", 2);
out = dest + 2;
}
}
if ((len = git__utf16_to_8(out, GIT_WIN_PATH_UTF8, src)) < 0)
return len;
git_path_mkposix(dest);
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;
}
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_git_path_w32_h__
#define INCLUDE_git_path_w32_h__
/*
* Provides a large enough buffer to support Windows paths: MAX_PATH is
* 260, corresponding to a maximum path length of 259 characters plus a
* NULL terminator. Prefixing with "\\?\" adds 4 characters, but if the
* original was a UNC path, then we turn "\\server\share" into
* "\\?\UNC\server\share". So we replace the first two characters with
* 8 characters, a net gain of 6, so the maximum length is MAX_PATH+6.
*/
#define GIT_WIN_PATH_UTF16 MAX_PATH+6
/* Maximum size of a UTF-8 Win32 path. We remove the "\\?\" or "\\?\UNC\"
* prefixes for presentation, bringing us back to 259 (non-NULL)
* characters. UTF-8 does have 4-byte sequences, but they are encoded in
* UTF-16 using surrogate pairs, which takes up the space of two characters.
* Two characters in the range U+0800 -> U+FFFF take up more space in UTF-8
* (6 bytes) than one surrogate pair (4 bytes).
*/
#define GIT_WIN_PATH_UTF8 (259 * 3 + 1)
/*
* 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];
/**
* Create a Win32 path (in UCS-2 format) from a UTF-8 string.
*
* @param dest The buffer to receive the wide string.
* @param src The UTF-8 string to convert.
* @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
*/
extern int git_win32_path_from_utf8(git_win32_path dest, const char *src);
/**
* Canonicalize a Win32 UCS-2 path so that it is suitable for delivery to the
* Win32 APIs: remove multiple directory separators, squashing to a single one,
* strip trailing directory separators, ensure directory separators are all
* canonical (always backslashes, never forward slashes) and process any
* directory entries of '.' or '..'.
*
* This processes the buffer in place.
*
* @param path The buffer to process
* @return The new length of the buffer, in wchar_t's (not counting the NULL terminator)
*/
extern int git_win32_path_canonicalize(git_win32_path path);
/**
* Create an internal format (posix-style) UTF-8 path from a Win32 UCS-2 path.
*
* @param dest The buffer to receive the UTF-8 string.
* @param src The wide string to convert.
* @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
*/
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
......@@ -9,6 +9,7 @@
#include "common.h"
#include "../posix.h"
#include "path_w32.h"
#include "utf-conv.h"
#include "dir.h"
......
......@@ -7,6 +7,7 @@
#include "../posix.h"
#include "../fileops.h"
#include "path.h"
#include "path_w32.h"
#include "utf-conv.h"
#include "repository.h"
#include "reparse.h"
......@@ -35,22 +36,6 @@
/* GetFinalPathNameByHandleW signature */
typedef DWORD(WINAPI *PFGetFinalPathNameByHandleW)(HANDLE, LPWSTR, DWORD, DWORD);
/* Helper function which converts UTF-8 paths to UTF-16.
* On failure, errno is set. */
static int utf8_to_16_with_errno(git_win32_path dest, const char *src)
{
int len = git_win32_path_from_utf8(dest, src);
if (len < 0) {
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
errno = ENAMETOOLONG;
else
errno = EINVAL; /* Bad code point, presumably */
}
return len;
}
int p_ftruncate(int fd, long size)
{
#if defined(_MSC_VER) && _MSC_VER >= 1500
......@@ -66,7 +51,7 @@ int p_mkdir(const char *path, mode_t mode)
GIT_UNUSED(mode);
if (utf8_to_16_with_errno(buf, path) < 0)
if (git_win32_path_from_utf8(buf, path) < 0)
return -1;
return _wmkdir(buf);
......@@ -85,7 +70,7 @@ int p_unlink(const char *path)
git_win32_path buf;
int error;
if (utf8_to_16_with_errno(buf, path) < 0)
if (git_win32_path_from_utf8(buf, path) < 0)
return -1;
error = _wunlink(buf);
......@@ -292,7 +277,7 @@ static int do_lstat(const char *path, struct stat *buf, bool posixly_correct)
git_win32_path path_w;
int len;
if ((len = utf8_to_16_with_errno(path_w, path)) < 0)
if ((len = git_win32_path_from_utf8(path_w, path)) < 0)
return -1;
git_win32__path_trim_end(path_w, len);
......@@ -323,7 +308,7 @@ int p_readlink(const char *path, char *buf, size_t bufsiz)
* could occur in the middle of the encoding of a code point,
* we need to buffer the result on the stack. */
if (utf8_to_16_with_errno(path_w, path) < 0 ||
if (git_win32_path_from_utf8(path_w, path) < 0 ||
readlink_w(target_w, path_w) < 0 ||
(len = git_win32_path_to_utf8(target, target_w)) < 0)
return -1;
......@@ -347,7 +332,7 @@ int p_open(const char *path, int flags, ...)
git_win32_path buf;
mode_t mode = 0;
if (utf8_to_16_with_errno(buf, path) < 0)
if (git_win32_path_from_utf8(buf, path) < 0)
return -1;
if (flags & O_CREAT) {
......@@ -365,7 +350,7 @@ int p_creat(const char *path, mode_t mode)
{
git_win32_path buf;
if (utf8_to_16_with_errno(buf, path) < 0)
if (git_win32_path_from_utf8(buf, path) < 0)
return -1;
return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | STANDARD_OPEN_FLAGS, mode);
......@@ -463,7 +448,7 @@ int p_stat(const char* path, struct stat* buf)
git_win32_path path_w;
int len;
if ((len = utf8_to_16_with_errno(path_w, path)) < 0)
if ((len = git_win32_path_from_utf8(path_w, path)) < 0)
return -1;
git_win32__path_trim_end(path_w, len);
......@@ -483,7 +468,7 @@ int p_chdir(const char* path)
{
git_win32_path buf;
if (utf8_to_16_with_errno(buf, path) < 0)
if (git_win32_path_from_utf8(buf, path) < 0)
return -1;
return _wchdir(buf);
......@@ -493,7 +478,7 @@ int p_chmod(const char* path, mode_t mode)
{
git_win32_path buf;
if (utf8_to_16_with_errno(buf, path) < 0)
if (git_win32_path_from_utf8(buf, path) < 0)
return -1;
return _wchmod(buf, mode);
......@@ -504,7 +489,7 @@ int p_rmdir(const char* path)
git_win32_path buf;
int error;
if (utf8_to_16_with_errno(buf, path) < 0)
if (git_win32_path_from_utf8(buf, path) < 0)
return -1;
error = _wrmdir(buf);
......@@ -533,7 +518,7 @@ char *p_realpath(const char *orig_path, char *buffer)
{
git_win32_path orig_path_w, buffer_w;
if (utf8_to_16_with_errno(orig_path_w, orig_path) < 0)
if (git_win32_path_from_utf8(orig_path_w, orig_path) < 0)
return NULL;
/* Note that if the path provided is a relative path, then the current directory
......@@ -554,20 +539,17 @@ char *p_realpath(const char *orig_path, char *buffer)
return NULL;
}
/* Convert the path to UTF-8. */
if (buffer) {
/* If the caller provided a buffer, then it is assumed to be GIT_WIN_PATH_UTF8
* characters in size. If it isn't, then we may overflow. */
if (git__utf16_to_8(buffer, GIT_WIN_PATH_UTF8, buffer_w) < 0)
return NULL;
} else {
/* If the caller did not provide a buffer, then we allocate one for the caller
* from the heap. */
if (git__utf16_to_8_alloc(&buffer, buffer_w) < 0)
return NULL;
if (!buffer && !(buffer = git__malloc(GIT_WIN_PATH_UTF8))) {
errno = ENOMEM;
return NULL;
}
/* Convert backslashes to forward slashes */
/* Convert the path to UTF-8. If the caller provided a buffer, then it
* is assumed to be GIT_WIN_PATH_UTF8 characters in size. If it isn't,
* then we may overflow. */
if (git_win32_path_to_utf8(buffer, buffer_w) < 0)
return NULL;
git_path_mkposix(buffer);
return buffer;
......@@ -608,6 +590,7 @@ int p_snprintf(char *buffer, size_t count, const char *format, ...)
return r;
}
/* TODO: wut? */
int p_mkstemp(char *tmp_path)
{
#if defined(_MSC_VER) && _MSC_VER >= 1500
......@@ -625,7 +608,7 @@ int p_access(const char* path, mode_t mode)
{
git_win32_path buf;
if (utf8_to_16_with_errno(buf, path) < 0)
if (git_win32_path_from_utf8(buf, path) < 0)
return -1;
return _waccess(buf, mode);
......@@ -664,8 +647,8 @@ int p_rename(const char *from, const char *to)
int rename_succeeded;
int error;
if (utf8_to_16_with_errno(wfrom, from) < 0 ||
utf8_to_16_with_errno(wto, to) < 0)
if (git_win32_path_from_utf8(wfrom, from) < 0 ||
git_win32_path_from_utf8(wto, to) < 0)
return -1;
/* wait up to 50ms if file is locked by another thread or process */
......
......@@ -26,6 +26,14 @@ GIT_INLINE(DWORD) get_wc_flags(void)
return flags;
}
GIT_INLINE(void) git__set_errno(void)
{
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
errno = ENAMETOOLONG;
else
errno = EINVAL;
}
/**
* Converts a UTF-8 string to wide characters.
*
......@@ -36,10 +44,15 @@ GIT_INLINE(DWORD) get_wc_flags(void)
*/
int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src)
{
int len;
/* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to
* turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's
* length. MultiByteToWideChar never returns int's minvalue, so underflow is not possible */
return MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size) - 1;
if ((len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size) - 1) < 0)
git__set_errno();
return len;
}
/**
......@@ -52,10 +65,15 @@ int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src)
*/
int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src)
{
int len;
/* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to
* turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's
* length. WideCharToMultiByte never returns int's minvalue, so underflow is not possible */
return WideCharToMultiByte(CP_UTF8, get_wc_flags(), src, -1, dest, (int)dest_size, NULL, NULL) - 1;
if ((len = WideCharToMultiByte(CP_UTF8, get_wc_flags(), src, -1, dest, (int)dest_size, NULL, NULL) - 1) < 0)
git__set_errno();
return len;
}
/**
......@@ -76,17 +94,23 @@ int git__utf8_to_16_alloc(wchar_t **dest, const char *src)
/* Length of -1 indicates NULL termination of the input string */
utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, NULL, 0);
if (!utf16_size)
if (!utf16_size) {
git__set_errno();
return -1;
}
*dest = git__malloc(utf16_size * sizeof(wchar_t));
if (!*dest)
if (!*dest) {
errno = ENOMEM;
return -1;
}
utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, *dest, utf16_size);
if (!utf16_size) {
git__set_errno();
git__free(*dest);
*dest = NULL;
}
......@@ -116,17 +140,23 @@ int git__utf16_to_8_alloc(char **dest, const wchar_t *src)
/* Length of -1 indicates NULL termination of the input string */
utf8_size = WideCharToMultiByte(CP_UTF8, dwFlags, src, -1, NULL, 0, NULL, NULL);
if (!utf8_size)
if (!utf8_size) {
git__set_errno();
return -1;
}
*dest = git__malloc(utf8_size);
if (!*dest)
if (!*dest) {
errno = ENOMEM;
return -1;
}
utf8_size = WideCharToMultiByte(CP_UTF8, dwFlags, src, -1, *dest, utf8_size, NULL, NULL);
if (!utf8_size) {
git__set_errno();
git__free(*dest);
*dest = NULL;
}
......
......@@ -10,21 +10,6 @@
#include <wchar.h>
#include "common.h"
/* Equal to the Win32 MAX_PATH constant. The maximum path length is 259
* characters plus a NULL terminator. */
#define GIT_WIN_PATH_UTF16 260
/* Maximum size of a UTF-8 Win32 path. UTF-8 does have 4-byte sequences,
* but they are encoded in UTF-16 using surrogate pairs, which takes up
* the space of two characters. Two characters in the range U+0800 ->
* U+FFFF take up more space in UTF-8 (6 bytes) than one surrogate pair
* (4 bytes). */
#define GIT_WIN_PATH_UTF8 (259 * 3 + 1)
/* Win32 path types */
typedef wchar_t git_win32_path[GIT_WIN_PATH_UTF16];
typedef char git_win32_utf8_path[GIT_WIN_PATH_UTF8];
/**
* Converts a UTF-8 string to wide characters.
*
......@@ -67,28 +52,4 @@ int git__utf8_to_16_alloc(wchar_t **dest, const char *src);
*/
int git__utf16_to_8_alloc(char **dest, const wchar_t *src);
/**
* Converts a UTF-8 Win32 path to wide characters.
*
* @param dest The buffer to receive the wide string.
* @param src The UTF-8 string to convert.
* @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
*/
GIT_INLINE(int) git_win32_path_from_utf8(git_win32_path dest, const char *src)
{
return git__utf8_to_16(dest, GIT_WIN_PATH_UTF16, src);
}
/**
* Converts a wide Win32 path to UTF-8.
*
* @param dest The buffer to receive the UTF-8 string.
* @param src The wide string to convert.
* @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
*/
GIT_INLINE(int) git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src)
{
return git__utf16_to_8(dest, GIT_WIN_PATH_UTF8, src);
}
#endif
......@@ -9,6 +9,7 @@
#define INCLUDE_w32_util_h__
#include "utf-conv.h"
#include "path_w32.h"
GIT_INLINE(bool) git_win32__isalpha(wchar_t c)
{
......
......@@ -11,6 +11,7 @@
#include <string.h>
#include <math.h>
#include <stdarg.h>
#include <wchar.h>
/* required for sandboxing */
#include <sys/types.h>
......@@ -525,6 +526,41 @@ void clar__assert_equal(
}
}
}
else if (!strcmp("%ls", fmt)) {
const wchar_t *wcs1 = va_arg(args, const wchar_t *);
const wchar_t *wcs2 = va_arg(args, const wchar_t *);
is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1, wcs2);
if (!is_equal) {
if (wcs1 && wcs2) {
int pos;
for (pos = 0; wcs1[pos] == wcs2[pos] && wcs1[pos] && wcs2[pos]; ++pos)
/* find differing byte offset */;
p_snprintf(buf, sizeof(buf), "'%ls' != '%ls' (at byte %d)",
wcs1, wcs2, pos);
} else {
p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'", wcs1, wcs2);
}
}
}
else if(!strcmp("%.*ls", fmt)) {
const wchar_t *wcs1 = va_arg(args, const wchar_t *);
const wchar_t *wcs2 = va_arg(args, const wchar_t *);
int len = va_arg(args, int);
is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1, wcs2, len);
if (!is_equal) {
if (wcs1 && wcs2) {
int pos;
for (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++pos)
/* find differing byte offset */;
p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls' (at byte %d)",
len, wcs1, len, wcs2, pos);
} else {
p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls'", len, wcs1, len, wcs2);
}
}
}
else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) {
size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t);
is_equal = (sz1 == sz2);
......
......@@ -74,9 +74,15 @@ void cl_fixture_cleanup(const char *fixture_name);
#define cl_assert_equal_s(s1,s2) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%s", (s1), (s2))
#define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%s", (s1), (s2))
#define cl_assert_equal_wcs(wcs1,wcs2) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%ls", (wcs1), (wcs2))
#define cl_assert_equal_wcs_(wcs1,wcs2,note) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%ls", (wcs1), (wcs2))
#define cl_assert_equal_strn(s1,s2,len) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%.*s", (s1), (s2), (int)(len))
#define cl_assert_equal_strn_(s1,s2,len,note) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%.*s", (s1), (s2), (int)(len))
#define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%.*ls", (wcs1), (wcs2), (int)(len))
#define cl_assert_equal_wcsn_(wcs1,wcs2,len,note) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%.*ls", (wcs1), (wcs2), (int)(len))
#define cl_assert_equal_i(i1,i2) clar__assert_equal(__FILE__,__LINE__,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2))
#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(__FILE__,__LINE__,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2))
#define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(__FILE__,__LINE__,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2))
......
......@@ -83,8 +83,8 @@ void test_config_include__depth(void)
cl_git_fail(git_config_open_ondisk(&cfg, "a"));
unlink("a");
unlink("b");
p_unlink("a");
p_unlink("b");
}
void test_config_include__missing(void)
......
......@@ -197,19 +197,6 @@ static void do_custom_reparse(const char *path)
#endif
git_buf *unslashify(git_buf *buf)
{
#ifdef GIT_WIN32
size_t i;
for (i = 0; i < buf->size; i++)
if (buf->ptr[i] == '/')
buf->ptr[i] = '\\';
#endif
return buf;
}
void test_core_link__stat_regular_file(void)
{
struct stat st;
......@@ -584,7 +571,7 @@ void test_core_link__readlink_symlink(void)
buf[len] = 0;
cl_assert_equal_s(git_buf_cstr(unslashify(&target_path)), buf);
cl_assert_equal_s(git_buf_cstr(&target_path), buf);
git_buf_free(&target_path);
}
......@@ -607,7 +594,7 @@ void test_core_link__readlink_dangling(void)
buf[len] = 0;
cl_assert_equal_s(git_buf_cstr(unslashify(&target_path)), buf);
cl_assert_equal_s(git_buf_cstr(&target_path), buf);
git_buf_free(&target_path);
}
......@@ -636,7 +623,7 @@ void test_core_link__readlink_multiple(void)
buf[len] = 0;
cl_assert_equal_s(git_buf_cstr(unslashify(&path2)), buf);
cl_assert_equal_s(git_buf_cstr(&path2), buf);
git_buf_free(&path1);
git_buf_free(&path2);
......
......@@ -309,31 +309,137 @@ void test_index_tests__add_bypath_to_a_bare_repository_returns_EBAREPO(void)
git_repository_free(bare_repo);
}
static void add_invalid_filename(git_repository *repo, const char *fn)
{
git_index *index;
git_buf path = GIT_BUF_INIT;
cl_git_pass(git_repository_index(&index, repo));
cl_assert(git_index_entrycount(index) == 0);
git_buf_joinpath(&path, "./invalid", fn);
cl_git_mkfile(path.ptr, NULL);
cl_git_fail(git_index_add_bypath(index, fn));
cl_must_pass(p_unlink(path.ptr));
cl_assert(git_index_entrycount(index) == 0);
git_index_free(index);
}
/* Test that writing an invalid filename fails */
void test_index_tests__write_invalid_filename(void)
void test_index_tests__add_invalid_filename(void)
{
git_repository *repo;
p_mkdir("invalid", 0700);
cl_git_pass(git_repository_init(&repo, "./invalid", 0));
cl_must_pass(p_mkdir("./invalid/subdir", 0777));
add_invalid_filename(repo, ".git/hello");
add_invalid_filename(repo, ".GIT/hello");
add_invalid_filename(repo, ".GiT/hello");
add_invalid_filename(repo, "./.git/hello");
add_invalid_filename(repo, "./foo");
add_invalid_filename(repo, "./bar");
add_invalid_filename(repo, "subdir/../bar");
git_repository_free(repo);
cl_fixture_cleanup("invalid");
}
static void replace_char(char *str, char in, char out)
{
char *c = str;
while (*c++)
if (*c == in)
*c = out;
}
static void write_invalid_filename(git_repository *repo, const char *fn_orig)
{
git_index *index;
git_oid expected;
const git_index_entry *entry;
git_buf path = GIT_BUF_INIT;
char *fn;
p_mkdir("read_tree", 0700);
cl_git_pass(git_repository_init(&repo, "./read_tree", 0));
cl_git_pass(git_repository_index(&index, repo));
cl_assert(git_index_entrycount(index) == 0);
cl_git_mkfile("./read_tree/.git/hello", NULL);
/*
* Sneak a valid path into the index, we'll update it
* to an invalid path when we try to write the index.
*/
fn = git__strdup(fn_orig);
replace_char(fn, '/', '_');
git_buf_joinpath(&path, "./invalid", fn);
cl_git_mkfile(path.ptr, NULL);
cl_git_pass(git_index_add_bypath(index, ".git/hello"));
cl_git_pass(git_index_add_bypath(index, fn));
cl_assert(entry = git_index_get_bypath(index, fn, 0));
/* kids, don't try this at home */
replace_char((char *)entry->path, '_', '/');
/* write-tree */
cl_git_fail(git_index_write_tree(&expected, index));
p_unlink(path.ptr);
cl_git_pass(git_index_remove_all(index, NULL, NULL, NULL));
git_index_free(index);
git__free(fn);
}
/* Test that writing an invalid filename fails */
void test_index_tests__write_invalid_filename(void)
{
git_repository *repo;
p_mkdir("invalid", 0700);
cl_git_pass(git_repository_init(&repo, "./invalid", 0));
write_invalid_filename(repo, ".git/hello");
write_invalid_filename(repo, ".GIT/hello");
write_invalid_filename(repo, ".GiT/hello");
write_invalid_filename(repo, "./.git/hello");
write_invalid_filename(repo, "./foo");
write_invalid_filename(repo, "./bar");
write_invalid_filename(repo, "foo/../bar");
git_repository_free(repo);
cl_fixture_cleanup("invalid");
}
void test_index_tests__honors_protect_filesystems(void)
{
git_repository *repo;
p_mkdir("invalid", 0700);
cl_git_pass(git_repository_init(&repo, "./invalid", 0));
cl_repo_set_bool(repo, "core.protectHFS", true);
cl_repo_set_bool(repo, "core.protectNTFS", true);
write_invalid_filename(repo, ".git./hello");
write_invalid_filename(repo, ".git\xe2\x80\xad/hello");
write_invalid_filename(repo, "git~1/hello");
write_invalid_filename(repo, ".git\xe2\x81\xaf/hello");
git_repository_free(repo);
cl_fixture_cleanup("read_tree");
cl_fixture_cleanup("invalid");
}
void test_index_tests__remove_entry(void)
......
#include "clar_libgit2.h"
#include "tree.h"
static git_repository *repo;
static const char *blob_oid = "3d0970ec547fc41ef8a5882dde99c6adce65b021";
static const char *tree_oid = "1b05fdaa881ee45b48cbaa5e9b037d667a47745e";
void test_object_tree_attributes__initialize(void)
{
repo = cl_git_sandbox_init("deprecated-mode.git");
}
void test_object_tree_attributes__cleanup(void)
{
cl_git_sandbox_cleanup();
}
void test_object_tree_attributes__ensure_correctness_of_attributes_on_insertion(void)
{
git_treebuilder *builder;
......@@ -11,7 +23,7 @@ void test_object_tree_attributes__ensure_correctness_of_attributes_on_insertion(
cl_git_pass(git_oid_fromstr(&oid, blob_oid));
cl_git_pass(git_treebuilder_create(&builder, NULL));
cl_git_pass(git_treebuilder_create(&builder, repo, NULL));
cl_git_fail(git_treebuilder_insert(NULL, builder, "one.txt", &oid, (git_filemode_t)0777777));
cl_git_fail(git_treebuilder_insert(NULL, builder, "one.txt", &oid, (git_filemode_t)0100666));
......@@ -22,7 +34,6 @@ void test_object_tree_attributes__ensure_correctness_of_attributes_on_insertion(
void test_object_tree_attributes__group_writable_tree_entries_created_with_an_antique_git_version_can_still_be_accessed(void)
{
git_repository *repo;
git_oid tid;
git_tree *tree;
const git_tree_entry *entry;
......@@ -38,7 +49,6 @@ void test_object_tree_attributes__group_writable_tree_entries_created_with_an_an
git_tree_entry_filemode(entry));
git_tree_free(tree);
git_repository_free(repo);
}
void test_object_tree_attributes__treebuilder_reject_invalid_filemode(void)
......@@ -48,7 +58,7 @@ void test_object_tree_attributes__treebuilder_reject_invalid_filemode(void)
const git_tree_entry *entry;
cl_git_pass(git_oid_fromstr(&bid, blob_oid));
cl_git_pass(git_treebuilder_create(&builder, NULL));
cl_git_pass(git_treebuilder_create(&builder, repo, NULL));
cl_git_fail(git_treebuilder_insert(
&entry,
......@@ -62,25 +72,22 @@ void test_object_tree_attributes__treebuilder_reject_invalid_filemode(void)
void test_object_tree_attributes__normalize_attributes_when_creating_a_tree_from_an_existing_one(void)
{
git_repository *repo;
git_treebuilder *builder;
git_oid tid, tid2;
git_tree *tree;
const git_tree_entry *entry;
repo = cl_git_sandbox_init("deprecated-mode.git");
cl_git_pass(git_oid_fromstr(&tid, tree_oid));
cl_git_pass(git_tree_lookup(&tree, repo, &tid));
cl_git_pass(git_treebuilder_create(&builder, tree));
cl_git_pass(git_treebuilder_create(&builder, repo, tree));
entry = git_treebuilder_get(builder, "old_mode.txt");
cl_assert_equal_i(
GIT_FILEMODE_BLOB,
git_tree_entry_filemode(entry));
cl_git_pass(git_treebuilder_write(&tid2, repo, builder));
cl_git_pass(git_treebuilder_write(&tid2, builder));
git_treebuilder_free(builder);
git_tree_free(tree);
......@@ -91,18 +98,14 @@ void test_object_tree_attributes__normalize_attributes_when_creating_a_tree_from
git_tree_entry_filemode(entry));
git_tree_free(tree);
cl_git_sandbox_cleanup();
}
void test_object_tree_attributes__normalize_600(void)
{
git_oid id;
git_tree *tree;
git_repository *repo;
const git_tree_entry *entry;
repo = cl_git_sandbox_init("deprecated-mode.git");
git_oid_fromstr(&id, "0810fb7818088ff5ac41ee49199b51473b1bd6c7");
cl_git_pass(git_tree_lookup(&tree, repo, &id));
......@@ -111,5 +114,4 @@ void test_object_tree_attributes__normalize_600(void)
cl_assert_equal_i(git_tree_entry_filemode_raw(entry), 0100600);
git_tree_free(tree);
cl_git_sandbox_cleanup();
}
......@@ -57,11 +57,11 @@ static void tree_creator(git_oid *out, void (*fn)(git_treebuilder *))
{
git_treebuilder *builder;
cl_git_pass(git_treebuilder_create(&builder, NULL));
cl_git_pass(git_treebuilder_create(&builder, _repo, NULL));
fn(builder);
cl_git_pass(git_treebuilder_write(out, _repo, builder));
cl_git_pass(git_treebuilder_write(out, builder));
git_treebuilder_free(builder);
}
......
......@@ -35,7 +35,7 @@ void test_object_tree_write__from_memory(void)
* on REPOSITORY_FOLDER.
*/
cl_git_pass(git_tree_lookup(&tree, g_repo, &id));
cl_git_pass(git_treebuilder_create(&builder, tree));
cl_git_pass(git_treebuilder_create(&builder, g_repo, tree));
cl_git_fail(git_treebuilder_insert(NULL, builder, "",
&bid, GIT_FILEMODE_BLOB));
......@@ -53,7 +53,7 @@ void test_object_tree_write__from_memory(void)
cl_git_pass(git_treebuilder_insert(
NULL, builder, "new.txt", &bid, GIT_FILEMODE_BLOB));
cl_git_pass(git_treebuilder_write(&rid, g_repo, builder));
cl_git_pass(git_treebuilder_write(&rid, builder));
cl_assert(git_oid_cmp(&rid, &id2) == 0);
......@@ -75,18 +75,18 @@ void test_object_tree_write__subtree(void)
git_oid_fromstr(&bid, blob_oid);
/* create subtree */
cl_git_pass(git_treebuilder_create(&builder, NULL));
cl_git_pass(git_treebuilder_create(&builder, g_repo, NULL));
cl_git_pass(git_treebuilder_insert(
NULL, builder, "new.txt", &bid, GIT_FILEMODE_BLOB)); /* -V536 */
cl_git_pass(git_treebuilder_write(&subtree_id, g_repo, builder));
cl_git_pass(git_treebuilder_write(&subtree_id, builder));
git_treebuilder_free(builder);
/* create parent tree */
cl_git_pass(git_tree_lookup(&tree, g_repo, &id));
cl_git_pass(git_treebuilder_create(&builder, tree));
cl_git_pass(git_treebuilder_create(&builder, g_repo, tree));
cl_git_pass(git_treebuilder_insert(
NULL, builder, "new", &subtree_id, GIT_FILEMODE_TREE)); /* -V536 */
cl_git_pass(git_treebuilder_write(&id_hiearar, g_repo, builder));
cl_git_pass(git_treebuilder_write(&id_hiearar, builder));
git_treebuilder_free(builder);
git_tree_free(tree);
......@@ -135,14 +135,14 @@ void test_object_tree_write__sorted_subtrees(void)
memset(&blank_oid, 0x0, sizeof(blank_oid));
cl_git_pass(git_treebuilder_create(&builder, NULL));
cl_git_pass(git_treebuilder_create(&builder, g_repo, NULL));
for (i = 0; i < ARRAY_SIZE(entries); ++i) {
cl_git_pass(git_treebuilder_insert(NULL,
builder, entries[i].filename, &blank_oid, entries[i].attr));
}
cl_git_pass(git_treebuilder_write(&tree_oid, g_repo, builder));
cl_git_pass(git_treebuilder_write(&tree_oid, builder));
cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_oid));
for (i = 0; i < git_tree_entrycount(tree); i++) {
......@@ -192,7 +192,7 @@ void test_object_tree_write__removing_and_re_adding_in_treebuilder(void)
memset(&blank_oid, 0x0, sizeof(blank_oid));
cl_git_pass(git_treebuilder_create(&builder, NULL));
cl_git_pass(git_treebuilder_create(&builder, g_repo, NULL));
cl_assert_equal_i(0, (int)git_treebuilder_entrycount(builder));
......@@ -229,7 +229,7 @@ void test_object_tree_write__removing_and_re_adding_in_treebuilder(void)
NULL, builder, "apple_extra", &blank_oid, GIT_FILEMODE_BLOB));
cl_assert_equal_i(7, (int)git_treebuilder_entrycount(builder));
cl_git_pass(git_treebuilder_write(&tree_oid, g_repo, builder));
cl_git_pass(git_treebuilder_write(&tree_oid, builder));
git_treebuilder_free(builder);
......@@ -283,7 +283,7 @@ void test_object_tree_write__filtering(void)
memset(&blank_oid, 0x0, sizeof(blank_oid));
cl_git_pass(git_treebuilder_create(&builder, NULL));
cl_git_pass(git_treebuilder_create(&builder, g_repo, NULL));
for (i = 0; _entries[i].filename; ++i)
cl_git_pass(git_treebuilder_insert(NULL,
......@@ -310,7 +310,7 @@ void test_object_tree_write__filtering(void)
cl_assert(git_treebuilder_get(builder, "aardvark") == NULL);
cl_assert(git_treebuilder_get(builder, "last") != NULL);
cl_git_pass(git_treebuilder_write(&tree_oid, g_repo, builder));
cl_git_pass(git_treebuilder_write(&tree_oid, builder));
git_treebuilder_free(builder);
......@@ -346,13 +346,13 @@ void test_object_tree_write__cruel_paths(void)
git_oid_fromstr(&bid, blob_oid);
/* create tree */
cl_git_pass(git_treebuilder_create(&builder, NULL));
cl_git_pass(git_treebuilder_create(&builder, g_repo, NULL));
for (scan = the_paths; *scan; ++scan) {
cl_git_pass(git_treebuilder_insert(
NULL, builder, *scan, &bid, GIT_FILEMODE_BLOB));
count++;
}
cl_git_pass(git_treebuilder_write(&id, g_repo, builder));
cl_git_pass(git_treebuilder_write(&id, builder));
git_treebuilder_free(builder);
/* check data is correct */
......@@ -374,12 +374,12 @@ void test_object_tree_write__cruel_paths(void)
git_tree_free(tree);
/* let's try longer paths */
cl_git_pass(git_treebuilder_create(&builder, NULL));
cl_git_pass(git_treebuilder_create(&builder, g_repo, NULL));
for (scan = the_paths; *scan; ++scan) {
cl_git_pass(git_treebuilder_insert(
NULL, builder, *scan, &id, GIT_FILEMODE_TREE));
}
cl_git_pass(git_treebuilder_write(&subid, g_repo, builder));
cl_git_pass(git_treebuilder_write(&subid, builder));
git_treebuilder_free(builder);
/* check data is correct */
......@@ -400,3 +400,43 @@ void test_object_tree_write__cruel_paths(void)
git_tree_free(tree);
}
void test_object_tree_write__protect_filesystems(void)
{
git_treebuilder *builder;
git_oid bid;
/* Ensure that (by default) we can write objects with funny names on
* platforms that are not affected.
*/
cl_git_pass(git_treebuilder_create(&builder, g_repo, NULL));
#ifndef GIT_WIN32
cl_git_pass(git_treebuilder_insert(NULL, builder, ".git.", &bid, GIT_FILEMODE_BLOB));
cl_git_pass(git_treebuilder_insert(NULL, builder, "git~1", &bid, GIT_FILEMODE_BLOB));
#endif
#ifndef __APPLE__
cl_git_pass(git_treebuilder_insert(NULL, builder, ".git\xef\xbb\xbf", &bid, GIT_FILEMODE_BLOB));
cl_git_pass(git_treebuilder_insert(NULL, builder, ".git\xe2\x80\xad", &bid, GIT_FILEMODE_BLOB));
#endif
git_treebuilder_free(builder);
/* Now turn on core.protectHFS and core.protectNTFS and validate that these
* paths are rejected.
*/
cl_repo_set_bool(g_repo, "core.protectHFS", true);
cl_repo_set_bool(g_repo, "core.protectNTFS", true);
cl_git_pass(git_treebuilder_create(&builder, g_repo, NULL));
cl_git_fail(git_treebuilder_insert(NULL, builder, ".git.", &bid, GIT_FILEMODE_BLOB));
cl_git_fail(git_treebuilder_insert(NULL, builder, "git~1", &bid, GIT_FILEMODE_BLOB));
cl_git_fail(git_treebuilder_insert(NULL, builder, ".git\xef\xbb\xbf", &bid, GIT_FILEMODE_BLOB));
cl_git_fail(git_treebuilder_insert(NULL, builder, ".git\xe2\x80\xad", &bid, GIT_FILEMODE_BLOB));
git_treebuilder_free(builder);
}
#include "clar_libgit2.h"
#include "path.h"
#ifdef GIT_WIN32
#include "win32/path_w32.h"
#endif
void test_utf8_to_utf16(const char *utf8_in, const wchar_t *utf16_expected)
{
#ifdef GIT_WIN32
git_win32_path path_utf16;
int path_utf16len;
cl_assert((path_utf16len = git_win32_path_from_utf8(path_utf16, utf8_in)) >= 0);
cl_assert_equal_wcs(utf16_expected, path_utf16);
cl_assert_equal_i(wcslen(utf16_expected), path_utf16len);
#else
GIT_UNUSED(utf8_in);
GIT_UNUSED(utf16_expected);
#endif
}
void test_path_win32__utf8_to_utf16(void)
{
#ifdef GIT_WIN32
test_utf8_to_utf16("C:\\", L"\\\\?\\C:\\");
test_utf8_to_utf16("c:\\", L"\\\\?\\c:\\");
test_utf8_to_utf16("C:/", L"\\\\?\\C:\\");
test_utf8_to_utf16("c:/", L"\\\\?\\c:\\");
#endif
}
void test_path_win32__removes_trailing_slash(void)
{
#ifdef GIT_WIN32
test_utf8_to_utf16("C:\\Foo\\", L"\\\\?\\C:\\Foo");
test_utf8_to_utf16("C:\\Foo\\\\", L"\\\\?\\C:\\Foo");
test_utf8_to_utf16("C:\\Foo\\\\", L"\\\\?\\C:\\Foo");
test_utf8_to_utf16("C:/Foo/", L"\\\\?\\C:\\Foo");
test_utf8_to_utf16("C:/Foo///", L"\\\\?\\C:\\Foo");
#endif
}
void test_path_win32__squashes_multiple_slashes(void)
{
#ifdef GIT_WIN32
test_utf8_to_utf16("C:\\\\Foo\\Bar\\\\Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar");
test_utf8_to_utf16("C://Foo/Bar///Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar");
#endif
}
void test_path_win32__unc(void)
{
#ifdef GIT_WIN32
test_utf8_to_utf16("\\\\server\\c$\\unc\\path", L"\\\\?\\UNC\\server\\c$\\unc\\path");
test_utf8_to_utf16("//server/git/style/unc/path", L"\\\\?\\UNC\\server\\git\\style\\unc\\path");
#endif
}
void test_path_win32__honors_max_path(void)
{
#ifdef GIT_WIN32
git_win32_path path_utf16;
test_utf8_to_utf16("C:\\This path is 259 chars and is the max length in windows\\0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij",
L"\\\\?\\C:\\This path is 259 chars and is the max length in windows\\0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij");
test_utf8_to_utf16("\\\\unc\\paths may also be 259 characters including the server\\123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij",
L"\\\\?\\UNC\\unc\\paths may also be 259 characters including the server\\123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij");
cl_check_fail(git_win32_path_from_utf8(path_utf16, "C:\\This path is 260 chars and is sadly too long for windows\\0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij"));
cl_check_fail(git_win32_path_from_utf8(path_utf16, "\\\\unc\\paths are also bound by 260 character restrictions\\including the server name portion\\bcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij"));
#endif
}
void test_path_win32__dot_and_dotdot(void)
{
#ifdef GIT_WIN32
test_utf8_to_utf16("C:\\Foo\\..\\Foobar", L"\\\\?\\C:\\Foobar");
test_utf8_to_utf16("C:\\Foo\\Bar\\..\\Foobar", L"\\\\?\\C:\\Foo\\Foobar");
test_utf8_to_utf16("C:\\Foo\\Bar\\..\\Foobar\\..", L"\\\\?\\C:\\Foo");
test_utf8_to_utf16("C:\\Foobar\\..", L"\\\\?\\C:\\");
test_utf8_to_utf16("C:/Foo/Bar/../Foobar", L"\\\\?\\C:\\Foo\\Foobar");
test_utf8_to_utf16("C:/Foo/Bar/../Foobar/../Asdf/", L"\\\\?\\C:\\Foo\\Asdf");
test_utf8_to_utf16("C:/Foo/Bar/../Foobar/..", L"\\\\?\\C:\\Foo");
test_utf8_to_utf16("C:/Foo/..", L"\\\\?\\C:\\");
test_utf8_to_utf16("C:\\Foo\\Bar\\.\\Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar");
test_utf8_to_utf16("C:\\.\\Foo\\.\\Bar\\.\\Foobar\\.\\", L"\\\\?\\C:\\Foo\\Bar\\Foobar");
test_utf8_to_utf16("C:/Foo/Bar/./Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar");
test_utf8_to_utf16("C:/Foo/../Bar/./Foobar/../", L"\\\\?\\C:\\Bar");
test_utf8_to_utf16("C:\\Foo\\..\\..\\Bar", L"\\\\?\\C:\\Bar");
#endif
}
void test_path_win32__absolute_from_no_drive_letter(void)
{
#ifdef GIT_WIN32
test_utf8_to_utf16("\\Foo", L"\\\\?\\C:\\Foo");
test_utf8_to_utf16("\\Foo\\Bar", L"\\\\?\\C:\\Foo\\Bar");
test_utf8_to_utf16("/Foo/Bar", L"\\\\?\\C:\\Foo\\Bar");
#endif
}
void test_path_win32__absolute_from_relative(void)
{
#ifdef GIT_WIN32
char cwd_backup[MAX_PATH];
cl_must_pass(p_getcwd(cwd_backup, MAX_PATH));
cl_must_pass(p_chdir("C:/"));
test_utf8_to_utf16("Foo", L"\\\\?\\C:\\Foo");
test_utf8_to_utf16("..\\..\\Foo", L"\\\\?\\C:\\Foo");
test_utf8_to_utf16("Foo\\..", L"\\\\?\\C:\\");
test_utf8_to_utf16("Foo\\..\\..", L"\\\\?\\C:\\");
test_utf8_to_utf16("", L"\\\\?\\C:\\");
cl_must_pass(p_chdir("C:/Windows"));
test_utf8_to_utf16("Foo", L"\\\\?\\C:\\Windows\\Foo");
test_utf8_to_utf16("Foo\\Bar", L"\\\\?\\C:\\Windows\\Foo\\Bar");
test_utf8_to_utf16("..\\Foo", L"\\\\?\\C:\\Foo");
test_utf8_to_utf16("Foo\\..\\Bar", L"\\\\?\\C:\\Windows\\Bar");
test_utf8_to_utf16("", L"\\\\?\\C:\\Windows");
cl_must_pass(p_chdir(cwd_backup));
#endif
}
void test_canonicalize(const wchar_t *in, const wchar_t *expected)
{
#ifdef GIT_WIN32
git_win32_path canonical;
cl_assert(wcslen(in) < MAX_PATH);
wcscpy(canonical, in);
cl_must_pass(git_win32_path_canonicalize(canonical));
cl_assert_equal_wcs(expected, canonical);
#else
GIT_UNUSED(in);
GIT_UNUSED(expected);
#endif
}
void test_path_win32__canonicalize(void)
{
#ifdef GIT_WIN32
test_canonicalize(L"C:\\Foo\\Bar", L"C:\\Foo\\Bar");
test_canonicalize(L"C:\\Foo\\", L"C:\\Foo");
test_canonicalize(L"C:\\Foo\\\\", L"C:\\Foo");
test_canonicalize(L"C:\\Foo\\..\\Bar", L"C:\\Bar");
test_canonicalize(L"C:\\Foo\\..\\..\\Bar", L"C:\\Bar");
test_canonicalize(L"C:\\Foo\\..\\..\\..\\..\\", L"C:\\");
test_canonicalize(L"C:/Foo/Bar", L"C:\\Foo\\Bar");
test_canonicalize(L"C:/", L"C:\\");
test_canonicalize(L"Foo\\\\Bar\\\\Asdf\\\\", L"Foo\\Bar\\Asdf");
test_canonicalize(L"Foo\\\\Bar\\\\..\\\\Asdf\\", L"Foo\\Asdf");
test_canonicalize(L"Foo\\\\Bar\\\\.\\\\Asdf\\", L"Foo\\Bar\\Asdf");
test_canonicalize(L"Foo\\\\..\\Bar\\\\.\\\\Asdf\\", L"Bar\\Asdf");
test_canonicalize(L"\\", L"");
test_canonicalize(L"", L"");
test_canonicalize(L"Foo\\..\\..\\..\\..", L"");
test_canonicalize(L"..\\..\\..\\..", L"");
test_canonicalize(L"\\..\\..\\..\\..", L"");
test_canonicalize(L"\\\\?\\C:\\Foo\\Bar", L"\\\\?\\C:\\Foo\\Bar");
test_canonicalize(L"\\\\?\\C:\\Foo\\Bar\\", L"\\\\?\\C:\\Foo\\Bar");
test_canonicalize(L"\\\\?\\C:\\\\Foo\\.\\Bar\\\\..\\", L"\\\\?\\C:\\Foo");
test_canonicalize(L"\\\\?\\C:\\\\", L"\\\\?\\C:\\");
test_canonicalize(L"//?/C:/", L"\\\\?\\C:\\");
test_canonicalize(L"//?/C:/../../Foo/", L"\\\\?\\C:\\Foo");
test_canonicalize(L"//?/C:/Foo/../../", L"\\\\?\\C:\\");
test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder", L"\\\\?\\UNC\\server\\C$\\folder");
test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder\\", L"\\\\?\\UNC\\server\\C$\\folder");
test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder\\", L"\\\\?\\UNC\\server\\C$\\folder");
test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder\\..\\..\\..\\..\\share\\", L"\\\\?\\UNC\\server\\share");
test_canonicalize(L"\\\\server\\share", L"\\\\server\\share");
test_canonicalize(L"\\\\server\\share\\", L"\\\\server\\share");
test_canonicalize(L"\\\\server\\share\\\\foo\\\\bar", L"\\\\server\\share\\foo\\bar");
test_canonicalize(L"\\\\server\\\\share\\\\foo\\\\bar", L"\\\\server\\share\\foo\\bar");
test_canonicalize(L"\\\\server\\share\\..\\foo", L"\\\\server\\foo");
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
}
......@@ -151,13 +151,11 @@ void test_refs_create__propagate_eexists(void)
cl_assert(error == GIT_EEXISTS);
}
void test_refs_create__creating_a_reference_with_an_invalid_name_returns_EINVALIDSPEC(void)
static void test_invalid_name(const char *name)
{
git_reference *new_reference;
git_oid id;
const char *name = "refs/heads/inv@{id";
git_oid_fromstr(&id, current_master_tip);
cl_assert_equal_i(GIT_EINVALIDSPEC, git_reference_create(
......@@ -166,3 +164,47 @@ void test_refs_create__creating_a_reference_with_an_invalid_name_returns_EINVALI
cl_assert_equal_i(GIT_EINVALIDSPEC, git_reference_symbolic_create(
&new_reference, g_repo, name, current_head_target, 0, NULL, NULL));
}
void test_refs_create__creating_a_reference_with_an_invalid_name_returns_EINVALIDSPEC(void)
{
test_invalid_name("refs/heads/inv@{id");
test_invalid_name("refs/heads/back\\slash");
test_invalid_name("refs/heads/foo ");
test_invalid_name("refs/heads/foo /bar");
test_invalid_name("refs/heads/com1:bar/foo");
test_invalid_name("refs/heads/e:");
test_invalid_name("refs/heads/c:/foo");
test_invalid_name("refs/heads/foo.");
}
static void test_win32_name(const char *name)
{
git_reference *new_reference = NULL;
git_oid id;
int ret;
git_oid_fromstr(&id, current_master_tip);
ret = git_reference_create(&new_reference, g_repo, name, &id, 0, NULL, NULL);
#ifdef GIT_WIN32
cl_assert_equal_i(GIT_EINVALIDSPEC, ret);
#else
cl_git_pass(ret);
#endif
git_reference_free(new_reference);
}
void test_refs_create__creating_a_loose_ref_with_invalid_windows_name(void)
{
test_win32_name("refs/heads/foo./bar");
test_win32_name("refs/heads/aux");
test_win32_name("refs/heads/aux.foo/bar");
test_win32_name("refs/heads/com1");
}
......@@ -427,7 +427,7 @@ static void build_test_tree(
git_buf name = GIT_BUF_INIT;
va_list arglist;
cl_git_pass(git_treebuilder_create(&builder, NULL)); /* start builder */
cl_git_pass(git_treebuilder_create(&builder, repo, NULL)); /* start builder */
va_start(arglist, fmt);
while (*scan) {
......@@ -451,7 +451,7 @@ static void build_test_tree(
}
va_end(arglist);
cl_git_pass(git_treebuilder_write(out, repo, builder));
cl_git_pass(git_treebuilder_write(out, builder));
git_treebuilder_free(builder);
git_buf_free(&name);
......
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