Bug#776839: unblock: libgit2/0.21.3-1.1
diff -Nru libgit2-0.21.1/debian/changelog libgit2-0.21.1/debian/changelog
--- libgit2-0.21.1/debian/changelog 2015-01-09 09:51:34.000000000 +1100
+++ libgit2-0.21.1/debian/changelog 2015-02-11 23:09:15.000000000 +1100
@@ -1,3 +1,10 @@
+libgit2 (0.21.1-3) jessie; urgency=medium
+
+ * Backported fix for case insensitive filesystems (CVE-2014-9390).
+ (Closes: #774048)
+
+ -- Russell Sim <russell.sim@gmail.com> Tue, 10 Feb 2015 20:29:05 +1100
+
libgit2 (0.21.1-2.1) jessie; urgency=medium
* Non-maintainer upload.
diff -Nru libgit2-0.21.1/debian/patches/CVE-2014-9390.patch libgit2-0.21.1/debian/patches/CVE-2014-9390.patch
--- libgit2-0.21.1/debian/patches/CVE-2014-9390.patch 1970-01-01 10:00:00.000000000 +1000
+++ libgit2-0.21.1/debian/patches/CVE-2014-9390.patch 2015-02-11 23:09:15.000000000 +1100
@@ -0,0 +1,2483 @@
+commit a86d224d78a3ac0f8a1901b0e9e2aee1e15d6f73
+Author: Edward Thomson <ethomson@microsoft.com>
+Date: Thu Dec 18 12:41:59 2014 -0600
+
+ index tests: test capitalization before mkdir
+
+commit 86b9eb3bf5dba342d0a5d805e6fe35c3e9c861cc
+Author: Carlos Martín Nieto <cmn@dwim.me>
+Date: Thu Dec 18 02:11:06 2014 +0100
+
+ Plug leaks
+
+commit 07164371d10109ba564835947a62fcedf288dce9
+Author: Carlos Martín Nieto <cmn@dwim.me>
+Date: Thu Dec 18 02:07:36 2014 +0100
+
+ Create miscapitialised dirs for case-sensitive filesystems
+
+ We need these directories to exist so cl_git_mkfile() can create the
+ files we ask it to.
+
+commit 5d5d6136aaeea22903ed5d30a858f8d106876771
+Author: Edward Thomson <ethomson@microsoft.com>
+Date: Tue Dec 16 18:53:55 2014 -0600
+
+ Introduce core.protectHFS and core.protectNTFS
+
+ Validate HFS ignored char ".git" paths when `core.protectHFS` is
+ specified. Validate NTFS invalid ".git" paths when `core.protectNTFS`
+ is specified.
+
+commit 2698e209d895856df9900899948269e2e490abd3
+Author: Vicent Marti <tanoku@gmail.com>
+Date: Tue Dec 16 13:03:02 2014 +0100
+
+ path: Use UTF8 iteration for HFS chars
+
+commit d7026dc574b79723008bba72989f74a801f4dfb5
+Author: Edward Thomson <ethomson@microsoft.com>
+Date: Wed Dec 10 19:12:16 2014 -0500
+
+ checkout: disallow bad paths on HFS
+
+ HFS filesystems ignore some characters like U+200C. When these
+ characters are included in a path, they will be ignored for the
+ purposes of comparison with other paths. Thus, if you have a ".git"
+ folder, a folder of ".git<U+200C>" will also match. Protect our
+ ".git" folder by ensuring that ".git<U+200C>" and friends do not match it.
+
+commit 37221f8cb02554297710f703047711a61e1169bb
+Author: Edward Thomson <ethomson@microsoft.com>
+Date: Tue Nov 25 18:13:00 2014 -0500
+
+ 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.
+
+commit cb6a309d8667310d3323f5b601a2f2fa893c37d0
+Author: Vicent Marti <tanoku@gmail.com>
+Date: Tue Nov 25 00:58:03 2014 +0100
+
+ index: Check for valid paths before creating an index entry
+
+commit 928a41d189f068010a32c6dea4bf921baa81d21c
+Author: Vicent Marti <tanoku@gmail.com>
+Date: Tue Nov 25 00:14:52 2014 +0100
+
+ tree: Check for `.git` with case insensitivy
+
+commit f45baf7a94a75cfb1855c9a750f38bbcfa22b199
+Author: Edward Thomson <ethomson@microsoft.com>
+Date: Mon Dec 1 13:09:58 2014 -0500
+
+ win32: use NT-prefixed "\\?\" paths
+
+ When turning UTF-8 paths into UCS-2 paths for Windows, always use
+ the \\?\-prefixed paths. Because this bypasses the system's
+ path canonicalization, handle the canonicalization functions ourselves.
+
+ We must:
+ 1. always use a backslash as a directory separator
+ 2. only use a single backslash between directories
+ 3. not rely on the system to translate "." and ".." in paths
+ 4. remove trailing backslashes, except at the drive root (C:\)
+
+commit 2e37e214e3d85da2a68476c7ae54051d525b05eb
+Author: Edward Thomson <ethomson@microsoft.com>
+Date: Mon Dec 1 13:06:11 2014 -0500
+
+ clar: wide character comparisons
+
+commit f2e46110c9f72d5eca539c76972b87003c5922be
+Author: Edward Thomson <ethomson@microsoft.com>
+Date: Wed Nov 26 16:24:37 2014 -0500
+
+ tests: use p_ instead of posix func directly
+diff --git a/src/checkout.c b/src/checkout.c
+index 20763fd..9adc6c6 100644
+--- a/src/checkout.c
++++ b/src/checkout.c
+@@ -1078,6 +1078,30 @@ done:
+ return error;
+ }
+
++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,
+@@ -1111,7 +1135,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;
+
+diff --git a/src/config_cache.c b/src/config_cache.c
+index 45c39ce..d397a4b 100644
+--- a/src/config_cache.c
++++ b/src/config_cache.c
+@@ -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)
+diff --git a/src/index.c b/src/index.c
+index b63a0be..db2a5e8 100644
+--- a/src/index.c
++++ b/src/index.c
+@@ -756,23 +756,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;
+@@ -784,14 +796,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);
+@@ -847,7 +862,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;
+
+@@ -856,11 +874,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;
+ }
+
+@@ -1125,7 +1143,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;
+
+@@ -1245,9 +1263,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++) {
+@@ -1764,7 +1782,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;
+ uint16_t flags_raw;
+@@ -1821,7 +1842,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;
+@@ -1924,7 +1945,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) {
+@@ -2263,6 +2284,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;
+@@ -2282,8 +2304,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;
+@@ -2323,6 +2345,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;
+@@ -2435,7 +2458,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;
+diff --git a/src/path.c b/src/path.c
+index 5beab97..3e2efea 100644
+--- a/src/path.c
++++ b/src/path.c
+@@ -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"
+@@ -1145,3 +1146,258 @@ int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path)
+
+ return 0;
+ }
++
++/* 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);
++}
+diff --git a/src/path.h b/src/path.h
+index 3e6efe3..6c50334 100644
+--- a/src/path.h
++++ b/src/path.h
+@@ -441,4 +441,47 @@ extern bool git_path_does_fs_decompose_unicode(const char *root);
+ /* Used for paths to repositories on the filesystem */
+ 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
+diff --git a/src/refdb_fs.c b/src/refdb_fs.c
+index 0e36ca8..682372f 100644
+--- a/src/refdb_fs.c
++++ b/src/refdb_fs.c
+@@ -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
+ */
+@@ -1573,6 +1578,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;
+
+diff --git a/src/repository.c b/src/repository.c
+index e8d50ae..647704b 100644
+--- a/src/repository.c
++++ b/src/repository.c
+@@ -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;
+diff --git a/src/repository.h b/src/repository.h
+index aba16a0..0718e64 100644
+--- a/src/repository.h
++++ b/src/repository.h
+@@ -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];
+@@ -172,4 +181,19 @@ GIT_INLINE(int) git_repository__ensure_not_bare(
+
+ 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
+diff --git a/src/tree.c b/src/tree.c
+index 4ddb26b..bfdcb82 100644
+--- a/src/tree.c
++++ b/src/tree.c
+@@ -55,7 +55,7 @@ static int valid_entry_name(const char *filename)
+ (*filename != '.' ||
+ (strcmp(filename, ".") != 0 &&
+ strcmp(filename, "..") != 0 &&
+- strcmp(filename, DOT_GIT) != 0));
++ strcasecmp(filename, DOT_GIT) != 0));
+ }
+
+ static int entry_sort_cmp(const void *a, const void *b)
+diff --git a/src/util.c b/src/util.c
+index f9d37e4..898db77 100644
+--- a/src/util.c
++++ b/src/util.c
+@@ -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);
+@@ -648,3 +663,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;
++}
+diff --git a/src/util.h b/src/util.h
+index ca676c0..ad1e21e 100644
+--- a/src/util.h
++++ b/src/util.h
+@@ -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.
+ */
+diff --git a/src/win32/findfile.c b/src/win32/findfile.c
+index 86d4ef5..de27dd0 100644
+--- a/src/win32/findfile.c
++++ b/src/win32/findfile.c
+@@ -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"
+diff --git a/src/win32/path_w32.c b/src/win32/path_w32.c
+new file mode 100644
+index 0000000..d66969c
+--- /dev/null
++++ b/src/win32/path_w32.c
+@@ -0,0 +1,305 @@
++/*
++ * 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;
++}
+diff --git a/src/win32/path_w32.h b/src/win32/path_w32.h
+new file mode 100644
+index 0000000..1d10166
+--- /dev/null
++++ b/src/win32/path_w32.h
+@@ -0,0 +1,80 @@
++/*
++ * 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
+diff --git a/src/win32/posix.h b/src/win32/posix.h
+index 2cbea18..ea56d4f 100644
+--- a/src/win32/posix.h
++++ b/src/win32/posix.h
+@@ -9,6 +9,7 @@
+
+ #include "common.h"
+ #include "../posix.h"
++#include "path_w32.h"
+ #include "utf-conv.h"
+ #include "dir.h"
+
+diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c
+index 3493843..0f91294 100644
+--- a/src/win32/posix_w32.c
++++ b/src/win32/posix_w32.c
+@@ -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"
+@@ -31,29 +32,13 @@
+ /* 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_mkdir(const char *path, mode_t mode)
+ {
+ git_win32_path buf;
+
+ 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);
+@@ -64,7 +49,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);
+@@ -260,7 +245,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);
+@@ -291,7 +276,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;
+@@ -315,7 +300,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) {
+@@ -333,7 +318,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);
+@@ -431,7 +416,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);
+@@ -451,7 +436,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);
+@@ -461,7 +446,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);
+@@ -472,7 +457,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);
+@@ -501,7 +486,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
+@@ -522,20 +507,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;
+@@ -568,6 +550,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)
+@@ -585,7 +568,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);
+@@ -599,8 +582,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 */
+diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c
+index b9ccfb5..b0205b0 100644
+--- a/src/win32/utf-conv.c
++++ b/src/win32/utf-conv.c
+@@ -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;
+ }
+diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h
+index a480cd9..89cdb96 100644
+--- a/src/win32/utf-conv.h
++++ b/src/win32/utf-conv.h
+@@ -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
+diff --git a/src/win32/w32_util.h b/src/win32/w32_util.h
+index a1d388a..9c1b943 100644
+--- a/src/win32/w32_util.h
++++ b/src/win32/w32_util.h
+@@ -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)
+ {
+diff --git a/tests/clar.c b/tests/clar.c
+index 1546447..51f1635 100644
+--- a/tests/clar.c
++++ b/tests/clar.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);
+diff --git a/tests/clar.h b/tests/clar.h
+index f9df72e..514203f 100644
+--- a/tests/clar.h
++++ b/tests/clar.h
+@@ -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))
+diff --git a/tests/config/include.c b/tests/config/include.c
+index 58bc690..88ea091 100644
+--- a/tests/config/include.c
++++ b/tests/config/include.c
+@@ -106,6 +106,6 @@ 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");
+ }
+diff --git a/tests/core/link.c b/tests/core/link.c
+index 1794a38..2674e35 100644
+--- a/tests/core/link.c
++++ b/tests/core/link.c
+@@ -196,19 +196,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;
+@@ -547,7 +534,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);
+ }
+@@ -567,7 +554,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);
+ }
+@@ -593,7 +580,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);
+diff --git a/tests/index/tests.c b/tests/index/tests.c
+index fa5c0bb..42f4483 100644
+--- a/tests/index/tests.c
++++ b/tests/index/tests.c
+@@ -309,31 +309,124 @@ 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_buf_free(&path);
++ 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));
++
++ /* cl_git_mkfile() needs the dir to exist */
++ if (!git_path_exists("./invalid/.GIT"))
++ cl_must_pass(p_mkdir("./invalid/.GIT", 0777));
++ if (!git_path_exists("./invalid/.GiT"))
++ cl_must_pass(p_mkdir("./invalid/.GiT", 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, fn));
++
++ cl_assert(entry = git_index_get_bypath(index, fn, 0));
+
+- cl_git_pass(git_index_add_bypath(index, ".git/hello"));
++ /* 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_buf_free(&path);
+ 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("read_tree");
++ cl_fixture_cleanup("invalid");
+ }
+
+ void test_index_tests__remove_entry(void)
+diff --git a/tests/path/core.c b/tests/path/core.c
+new file mode 100644
+index 0000000..8a29004
+--- /dev/null
++++ b/tests/path/core.c
+@@ -0,0 +1,241 @@
++#include "clar_libgit2.h"
++#include "path.h"
++
++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_dotgit_ntfs(void)
++{
++ cl_assert_equal_b(true, git_path_isvalid(NULL, ".git", 0));
++ cl_assert_equal_b(true, git_path_isvalid(NULL, ".git ", 0));
++ cl_assert_equal_b(true, git_path_isvalid(NULL, ".git.", 0));
++ cl_assert_equal_b(true, git_path_isvalid(NULL, ".git.. .", 0));
++
++ cl_assert_equal_b(true, git_path_isvalid(NULL, "git~1", 0));
++ cl_assert_equal_b(true, git_path_isvalid(NULL, "git~1 ", 0));
++ cl_assert_equal_b(true, git_path_isvalid(NULL, "git~1.", 0));
++ cl_assert_equal_b(true, git_path_isvalid(NULL, "git~1.. .", 0));
++
++ cl_assert_equal_b(false, git_path_isvalid(NULL, ".git", GIT_PATH_REJECT_DOT_GIT_NTFS));
++ cl_assert_equal_b(false, git_path_isvalid(NULL, ".git ", GIT_PATH_REJECT_DOT_GIT_NTFS));
++ cl_assert_equal_b(false, git_path_isvalid(NULL, ".git.", GIT_PATH_REJECT_DOT_GIT_NTFS));
++ cl_assert_equal_b(false, git_path_isvalid(NULL, ".git.. .", GIT_PATH_REJECT_DOT_GIT_NTFS));
++
++ cl_assert_equal_b(false, git_path_isvalid(NULL, "git~1", GIT_PATH_REJECT_DOT_GIT_NTFS));
++ cl_assert_equal_b(false, git_path_isvalid(NULL, "git~1 ", GIT_PATH_REJECT_DOT_GIT_NTFS));
++ cl_assert_equal_b(false, git_path_isvalid(NULL, "git~1.", GIT_PATH_REJECT_DOT_GIT_NTFS));
++ cl_assert_equal_b(false, git_path_isvalid(NULL, "git~1.. .", GIT_PATH_REJECT_DOT_GIT_NTFS));
++}
++
++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_path_core__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));
++}
++
++void test_path_core__isvalid_dotgit_with_hfs_ignorables(void)
++{
++ cl_assert_equal_b(false, git_path_isvalid(NULL, ".git", GIT_PATH_REJECT_DOT_GIT_HFS));
++ cl_assert_equal_b(false, git_path_isvalid(NULL, ".git\xe2\x80\x8c", GIT_PATH_REJECT_DOT_GIT_HFS));
++ cl_assert_equal_b(false, git_path_isvalid(NULL, ".gi\xe2\x80\x8dT", GIT_PATH_REJECT_DOT_GIT_HFS));
++ cl_assert_equal_b(false, git_path_isvalid(NULL, ".g\xe2\x80\x8eIt", GIT_PATH_REJECT_DOT_GIT_HFS));
++ cl_assert_equal_b(false, git_path_isvalid(NULL, ".\xe2\x80\x8fgIt", GIT_PATH_REJECT_DOT_GIT_HFS));
++ cl_assert_equal_b(false, git_path_isvalid(NULL, "\xe2\x80\xaa.gIt", GIT_PATH_REJECT_DOT_GIT_HFS));
++
++ cl_assert_equal_b(false, git_path_isvalid(NULL, "\xe2\x80\xab.\xe2\x80\xacG\xe2\x80\xadI\xe2\x80\xaet", GIT_PATH_REJECT_DOT_GIT_HFS));
++ cl_assert_equal_b(false, git_path_isvalid(NULL, "\xe2\x81\xab.\xe2\x80\xaaG\xe2\x81\xabI\xe2\x80\xact", GIT_PATH_REJECT_DOT_GIT_HFS));
++ cl_assert_equal_b(false, git_path_isvalid(NULL, "\xe2\x81\xad.\xe2\x80\xaeG\xef\xbb\xbfIT", GIT_PATH_REJECT_DOT_GIT_HFS));
++
++ cl_assert_equal_b(true, git_path_isvalid(NULL, ".", GIT_PATH_REJECT_DOT_GIT_HFS));
++ cl_assert_equal_b(true, git_path_isvalid(NULL, ".g", GIT_PATH_REJECT_DOT_GIT_HFS));
++ cl_assert_equal_b(true, git_path_isvalid(NULL, ".gi", GIT_PATH_REJECT_DOT_GIT_HFS));
++ cl_assert_equal_b(true, git_path_isvalid(NULL, " .git", GIT_PATH_REJECT_DOT_GIT_HFS));
++ cl_assert_equal_b(true, git_path_isvalid(NULL, "..git\xe2\x80\x8c", GIT_PATH_REJECT_DOT_GIT_HFS));
++ cl_assert_equal_b(true, git_path_isvalid(NULL, ".gi\xe2\x80\x8dT.", GIT_PATH_REJECT_DOT_GIT_HFS));
++ cl_assert_equal_b(true, git_path_isvalid(NULL, ".g\xe2\x80It", GIT_PATH_REJECT_DOT_GIT_HFS));
++ cl_assert_equal_b(true, git_path_isvalid(NULL, ".\xe2gIt", GIT_PATH_REJECT_DOT_GIT_HFS));
++ cl_assert_equal_b(true, git_path_isvalid(NULL, "\xe2\x80\xaa.gi", GIT_PATH_REJECT_DOT_GIT_HFS));
++ cl_assert_equal_b(true, git_path_isvalid(NULL, ".gi\x80\x8dT", GIT_PATH_REJECT_DOT_GIT_HFS));
++ cl_assert_equal_b(true, git_path_isvalid(NULL, ".gi\x8dT", GIT_PATH_REJECT_DOT_GIT_HFS));
++ cl_assert_equal_b(true, git_path_isvalid(NULL, ".g\xe2i\x80T\x8e", GIT_PATH_REJECT_DOT_GIT_HFS));
++ cl_assert_equal_b(true, git_path_isvalid(NULL, ".git\xe2\x80\xbf", GIT_PATH_REJECT_DOT_GIT_HFS));
++ cl_assert_equal_b(true, git_path_isvalid(NULL, ".git\xe2\xab\x81", GIT_PATH_REJECT_DOT_GIT_HFS));
++}
+diff --git a/tests/path/win32.c b/tests/path/win32.c
+new file mode 100644
+index 0000000..22742f8
+--- /dev/null
++++ b/tests/path/win32.c
+@@ -0,0 +1,214 @@
++
++#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
++}
diff -Nru libgit2-0.21.1/debian/patches/series libgit2-0.21.1/debian/patches/series
--- libgit2-0.21.1/debian/patches/series 2015-01-09 09:51:34.000000000 +1100
+++ libgit2-0.21.1/debian/patches/series 2015-02-11 23:09:15.000000000 +1100
@@ -1 +1,2 @@
disable_tests.patch
+CVE-2014-9390.patch
Reply to: