2121#include <stdio.h>
2222#include <ctype.h>
2323
24- #define LOOKS_LIKE_DRIVE_PREFIX (S ) (git__isalpha((S)[0]) && (S)[1] == ':')
24+ static int dos_drive_prefix_length (const char * path )
25+ {
26+ int i ;
27+
28+ /*
29+ * Does it start with an ASCII letter (i.e. highest bit not set),
30+ * followed by a colon?
31+ */
32+ if (!(0x80 & (unsigned char )* path ))
33+ return * path && path [1 ] == ':' ? 2 : 0 ;
34+
35+ /*
36+ * While drive letters must be letters of the English alphabet, it is
37+ * possible to assign virtually _any_ Unicode character via `subst` as
38+ * a drive letter to "virtual drives". Even `1`, or `ä`. Or fun stuff
39+ * like this:
40+ *
41+ * subst ֍: %USERPROFILE%\Desktop
42+ */
43+ for (i = 1 ; i < 4 && (0x80 & (unsigned char )path [i ]); i ++ )
44+ ; /* skip first UTF-8 character */
45+ return path [i ] == ':' ? i + 1 : 0 ;
46+ }
2547
2648#ifdef GIT_WIN32
2749static bool looks_like_network_computer_name (const char * path , int pos )
@@ -123,11 +145,11 @@ static int win32_prefix_length(const char *path, int len)
123145 GIT_UNUSED (len );
124146#else
125147 /*
126- * Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return
127- * 'C:/' here
148+ * Mimic unix behavior where '/.git' returns '/': 'C:/.git'
149+ * will return 'C:/' here
128150 */
129- if (len == 2 && LOOKS_LIKE_DRIVE_PREFIX ( path ) )
130- return 2 ;
151+ if (dos_drive_prefix_length ( path ) == len )
152+ return len ;
131153
132154 /*
133155 * Similarly checks if we're dealing with a network computer name
@@ -260,11 +282,11 @@ const char *git_path_topdir(const char *path)
260282
261283int git_path_root (const char * path )
262284{
263- int offset = 0 ;
285+ int offset = 0 , prefix_len ;
264286
265287 /* Does the root of the path look like a windows drive ? */
266- if (LOOKS_LIKE_DRIVE_PREFIX ( path ))
267- offset += 2 ;
288+ if (( prefix_len = dos_drive_prefix_length ( path ) ))
289+ offset += prefix_len ;
268290
269291#ifdef GIT_WIN32
270292 /* Are we dealing with a windows network path? */
@@ -1609,8 +1631,12 @@ GIT_INLINE(bool) verify_dotgit_ntfs(git_repository *repo, const char *path, size
16091631 if (!start )
16101632 return true;
16111633
1612- /* Reject paths like ".git\" */
1613- if (path [start ] == '\\' )
1634+ /*
1635+ * Reject paths that start with Windows-style directory separators
1636+ * (".git\") or NTFS alternate streams (".git:") and could be used
1637+ * to write to the ".git" directory on Windows platforms.
1638+ */
1639+ if (path [start ] == '\\' || path [start ] == ':' )
16141640 return false;
16151641
16161642 /* Reject paths like '.git ' or '.git.' */
@@ -1622,12 +1648,21 @@ GIT_INLINE(bool) verify_dotgit_ntfs(git_repository *repo, const char *path, size
16221648 return false;
16231649}
16241650
1625- GIT_INLINE (bool ) only_spaces_and_dots (const char * path )
1651+ /*
1652+ * Windows paths that end with spaces and/or dots are elided to the
1653+ * path without them for backward compatibility. That is to say
1654+ * that opening file "foo ", "foo." or even "foo . . ." will all
1655+ * map to a filename of "foo". This function identifies spaces and
1656+ * dots at the end of a filename, whether the proper end of the
1657+ * filename (end of string) or a colon (which would indicate a
1658+ * Windows alternate data stream.)
1659+ */
1660+ GIT_INLINE (bool ) ntfs_end_of_filename (const char * path )
16261661{
16271662 const char * c = path ;
16281663
16291664 for (;; c ++ ) {
1630- if (* c == '\0' )
1665+ if (* c == '\0' || * c == ':' )
16311666 return true;
16321667 if (* c != ' ' && * c != '.' )
16331668 return false;
@@ -1642,13 +1677,13 @@ GIT_INLINE(bool) verify_dotgit_ntfs_generic(const char *name, size_t len, const
16421677
16431678 if (name [0 ] == '.' && len >= dotgit_len &&
16441679 !strncasecmp (name + 1 , dotgit_name , dotgit_len )) {
1645- return !only_spaces_and_dots (name + dotgit_len + 1 );
1680+ return !ntfs_end_of_filename (name + dotgit_len + 1 );
16461681 }
16471682
16481683 /* Detect the basic NTFS shortname with the first six chars */
16491684 if (!strncasecmp (name , dotgit_name , 6 ) && name [6 ] == '~' &&
16501685 name [7 ] >= '1' && name [7 ] <= '4' )
1651- return !only_spaces_and_dots (name + 8 );
1686+ return !ntfs_end_of_filename (name + 8 );
16521687
16531688 /* Catch fallback names */
16541689 for (i = 0 , saw_tilde = 0 ; i < 8 ; i ++ ) {
@@ -1670,7 +1705,7 @@ GIT_INLINE(bool) verify_dotgit_ntfs_generic(const char *name, size_t len, const
16701705 }
16711706 }
16721707
1673- return !only_spaces_and_dots (name + i );
1708+ return !ntfs_end_of_filename (name + i );
16741709}
16751710
16761711GIT_INLINE (bool ) verify_char (unsigned char c , unsigned int flags )
@@ -1804,7 +1839,7 @@ GIT_INLINE(unsigned int) dotgit_flags(
18041839 git_repository * repo ,
18051840 unsigned int flags )
18061841{
1807- int protectHFS = 0 , protectNTFS = 0 ;
1842+ int protectHFS = 0 , protectNTFS = 1 ;
18081843 int error = 0 ;
18091844
18101845 flags |= GIT_PATH_REJECT_DOT_GIT_LITERAL ;
@@ -1813,16 +1848,12 @@ GIT_INLINE(unsigned int) dotgit_flags(
18131848 protectHFS = 1 ;
18141849#endif
18151850
1816- #ifdef GIT_WIN32
1817- protectNTFS = 1 ;
1818- #endif
1819-
18201851 if (repo && !protectHFS )
18211852 error = git_repository__cvar (& protectHFS , repo , GIT_CVAR_PROTECTHFS );
18221853 if (!error && protectHFS )
18231854 flags |= GIT_PATH_REJECT_DOT_GIT_HFS ;
18241855
1825- if (repo && ! protectNTFS )
1856+ if (repo )
18261857 error = git_repository__cvar (& protectNTFS , repo , GIT_CVAR_PROTECTNTFS );
18271858 if (!error && protectNTFS )
18281859 flags |= GIT_PATH_REJECT_DOT_GIT_NTFS ;
0 commit comments