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
@@ -272,11 +294,11 @@ const char *git_path_topdir(const char *path)
272294
273295int git_path_root (const char * path )
274296{
275- int offset = 0 ;
297+ int offset = 0 , prefix_len ;
276298
277299 /* Does the root of the path look like a windows drive ? */
278- if (LOOKS_LIKE_DRIVE_PREFIX ( path ))
279- offset += 2 ;
300+ if (( prefix_len = dos_drive_prefix_length ( path ) ))
301+ offset += prefix_len ;
280302
281303#ifdef GIT_WIN32
282304 /* Are we dealing with a windows network path? */
@@ -1624,8 +1646,12 @@ GIT_INLINE(bool) verify_dotgit_ntfs(git_repository *repo, const char *path, size
16241646 if (!start )
16251647 return true;
16261648
1627- /* Reject paths like ".git\" */
1628- if (path [start ] == '\\' )
1649+ /*
1650+ * Reject paths that start with Windows-style directory separators
1651+ * (".git\") or NTFS alternate streams (".git:") and could be used
1652+ * to write to the ".git" directory on Windows platforms.
1653+ */
1654+ if (path [start ] == '\\' || path [start ] == ':' )
16291655 return false;
16301656
16311657 /* Reject paths like '.git ' or '.git.' */
@@ -1637,12 +1663,21 @@ GIT_INLINE(bool) verify_dotgit_ntfs(git_repository *repo, const char *path, size
16371663 return false;
16381664}
16391665
1640- GIT_INLINE (bool ) only_spaces_and_dots (const char * path )
1666+ /*
1667+ * Windows paths that end with spaces and/or dots are elided to the
1668+ * path without them for backward compatibility. That is to say
1669+ * that opening file "foo ", "foo." or even "foo . . ." will all
1670+ * map to a filename of "foo". This function identifies spaces and
1671+ * dots at the end of a filename, whether the proper end of the
1672+ * filename (end of string) or a colon (which would indicate a
1673+ * Windows alternate data stream.)
1674+ */
1675+ GIT_INLINE (bool ) ntfs_end_of_filename (const char * path )
16411676{
16421677 const char * c = path ;
16431678
16441679 for (;; c ++ ) {
1645- if (* c == '\0' )
1680+ if (* c == '\0' || * c == ':' )
16461681 return true;
16471682 if (* c != ' ' && * c != '.' )
16481683 return false;
@@ -1657,13 +1692,13 @@ GIT_INLINE(bool) verify_dotgit_ntfs_generic(const char *name, size_t len, const
16571692
16581693 if (name [0 ] == '.' && len >= dotgit_len &&
16591694 !strncasecmp (name + 1 , dotgit_name , dotgit_len )) {
1660- return !only_spaces_and_dots (name + dotgit_len + 1 );
1695+ return !ntfs_end_of_filename (name + dotgit_len + 1 );
16611696 }
16621697
16631698 /* Detect the basic NTFS shortname with the first six chars */
16641699 if (!strncasecmp (name , dotgit_name , 6 ) && name [6 ] == '~' &&
16651700 name [7 ] >= '1' && name [7 ] <= '4' )
1666- return !only_spaces_and_dots (name + 8 );
1701+ return !ntfs_end_of_filename (name + 8 );
16671702
16681703 /* Catch fallback names */
16691704 for (i = 0 , saw_tilde = 0 ; i < 8 ; i ++ ) {
@@ -1685,7 +1720,7 @@ GIT_INLINE(bool) verify_dotgit_ntfs_generic(const char *name, size_t len, const
16851720 }
16861721 }
16871722
1688- return !only_spaces_and_dots (name + i );
1723+ return !ntfs_end_of_filename (name + i );
16891724}
16901725
16911726GIT_INLINE (bool ) verify_char (unsigned char c , unsigned int flags )
@@ -1819,7 +1854,7 @@ GIT_INLINE(unsigned int) dotgit_flags(
18191854 git_repository * repo ,
18201855 unsigned int flags )
18211856{
1822- int protectHFS = 0 , protectNTFS = 0 ;
1857+ int protectHFS = 0 , protectNTFS = 1 ;
18231858 int error = 0 ;
18241859
18251860 flags |= GIT_PATH_REJECT_DOT_GIT_LITERAL ;
@@ -1828,16 +1863,12 @@ GIT_INLINE(unsigned int) dotgit_flags(
18281863 protectHFS = 1 ;
18291864#endif
18301865
1831- #ifdef GIT_WIN32
1832- protectNTFS = 1 ;
1833- #endif
1834-
18351866 if (repo && !protectHFS )
18361867 error = git_repository__configmap_lookup (& protectHFS , repo , GIT_CONFIGMAP_PROTECTHFS );
18371868 if (!error && protectHFS )
18381869 flags |= GIT_PATH_REJECT_DOT_GIT_HFS ;
18391870
1840- if (repo && ! protectNTFS )
1871+ if (repo )
18411872 error = git_repository__configmap_lookup (& protectNTFS , repo , GIT_CONFIGMAP_PROTECTNTFS );
18421873 if (!error && protectNTFS )
18431874 flags |= GIT_PATH_REJECT_DOT_GIT_NTFS ;
0 commit comments