2 * Copyright (C) the libgit2 contributors. All rights reserved.
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
11 #include "repository.h"
13 #include "win32/posix.h"
14 #include "win32/w32_buffer.h"
15 #include "win32/w32_util.h"
16 #include "win32/version.h"
24 static int dos_drive_prefix_length(const char *path
)
29 * Does it start with an ASCII letter (i.e. highest bit not set),
30 * followed by a colon?
32 if (!(0x80 & (unsigned char)*path
))
33 return *path
&& path
[1] == ':' ? 2 : 0;
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
41 * subst ֍: %USERPROFILE%\Desktop
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;
49 static bool looks_like_network_computer_name(const char *path
, int pos
)
54 if (path
[0] != '/' || path
[1] != '/')
67 * Based on the Android implementation, BSD licensed.
68 * http://android.git.kernel.org/
70 * Copyright (C) 2008 The Android Open Source Project
71 * All rights reserved.
73 * Redistribution and use in source and binary forms, with or without
74 * modification, are permitted provided that the following conditions
76 * * Redistributions of source code must retain the above copyright
77 * notice, this list of conditions and the following disclaimer.
78 * * Redistributions in binary form must reproduce the above copyright
79 * notice, this list of conditions and the following disclaimer in
80 * the documentation and/or other materials provided with the
83 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
84 * AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
85 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
86 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
87 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
88 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
89 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
90 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
91 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
92 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
93 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
96 int git_path_basename_r(git_buf
*buffer
, const char *path
)
98 const char *endp
, *startp
;
101 /* Empty or NULL string gets treated as "." */
102 if (path
== NULL
|| *path
== '\0') {
108 /* Strip trailing slashes */
109 endp
= path
+ strlen(path
) - 1;
110 while (endp
> path
&& *endp
== '/')
113 /* All slashes becomes "/" */
114 if (endp
== path
&& *endp
== '/') {
120 /* Find the start of the base */
122 while (startp
> path
&& *(startp
- 1) != '/')
125 /* Cast is safe because max path < max int */
126 len
= (int)(endp
- startp
+ 1);
131 if (buffer
!= NULL
&& git_buf_set(buffer
, startp
, len
) < 0)
138 * Determine if the path is a Windows prefix and, if so, returns
139 * its actual lentgh. If it is not a prefix, returns -1.
141 static int win32_prefix_length(const char *path
, int len
)
148 * Mimic unix behavior where '/.git' returns '/': 'C:/.git'
149 * will return 'C:/' here
151 if (dos_drive_prefix_length(path
) == len
)
155 * Similarly checks if we're dealing with a network computer name
156 * '//computername/.git' will return '//computername/'
158 if (looks_like_network_computer_name(path
, len
))
166 * Based on the Android implementation, BSD licensed.
167 * Check http://android.git.kernel.org/
169 int git_path_dirname_r(git_buf
*buffer
, const char *path
)
172 int is_prefix
= 0, len
;
174 /* Empty or NULL string gets treated as "." */
175 if (path
== NULL
|| *path
== '\0') {
181 /* Strip trailing slashes */
182 endp
= path
+ strlen(path
) - 1;
183 while (endp
> path
&& *endp
== '/')
186 if (endp
- path
+ 1 > INT_MAX
) {
187 git_error_set(GIT_ERROR_INVALID
, "path too long");
192 if ((len
= win32_prefix_length(path
, (int)(endp
- path
+ 1))) > 0) {
197 /* Find the start of the dir */
198 while (endp
> path
&& *endp
!= '/')
201 /* Either the dir is "/" or there are no slashes */
203 path
= (*endp
== '/') ? "/" : ".";
210 } while (endp
> path
&& *endp
== '/');
212 if (endp
- path
+ 1 > INT_MAX
) {
213 git_error_set(GIT_ERROR_INVALID
, "path too long");
218 if ((len
= win32_prefix_length(path
, (int)(endp
- path
+ 1))) > 0) {
223 /* Cast is safe because max path < max int */
224 len
= (int)(endp
- path
+ 1);
228 if (git_buf_set(buffer
, path
, len
) < 0)
230 if (is_prefix
&& git_buf_putc(buffer
, '/') < 0)
238 char *git_path_dirname(const char *path
)
240 git_buf buf
= GIT_BUF_INIT
;
243 git_path_dirname_r(&buf
, path
);
244 dirname
= git_buf_detach(&buf
);
245 git_buf_dispose(&buf
); /* avoid memleak if error occurs */
250 char *git_path_basename(const char *path
)
252 git_buf buf
= GIT_BUF_INIT
;
255 git_path_basename_r(&buf
, path
);
256 basename
= git_buf_detach(&buf
);
257 git_buf_dispose(&buf
); /* avoid memleak if error occurs */
262 size_t git_path_basename_offset(git_buf
*buffer
)
266 if (!buffer
|| buffer
->size
<= 0)
269 slash
= git_buf_rfind_next(buffer
, '/');
271 if (slash
>= 0 && buffer
->ptr
[slash
] == '/')
272 return (size_t)(slash
+ 1);
277 const char *git_path_topdir(const char *path
)
285 if (!len
|| path
[len
- 1] != '/')
288 for (i
= (ssize_t
)len
- 2; i
>= 0; --i
)
295 int git_path_root(const char *path
)
297 int offset
= 0, prefix_len
;
299 /* Does the root of the path look like a windows drive ? */
300 if ((prefix_len
= dos_drive_prefix_length(path
)))
301 offset
+= prefix_len
;
304 /* Are we dealing with a windows network path? */
305 else if ((path
[0] == '/' && path
[1] == '/' && path
[2] != '/') ||
306 (path
[0] == '\\' && path
[1] == '\\' && path
[2] != '\\'))
310 /* Skip the computer name segment */
311 while (path
[offset
] && path
[offset
] != '/' && path
[offset
] != '\\')
315 if (path
[offset
] == '\\')
319 if (path
[offset
] == '/')
322 return -1; /* Not a real error - signals that path is not rooted */
325 static void path_trim_slashes(git_buf
*path
)
327 int ceiling
= git_path_root(path
->ptr
) + 1;
328 assert(ceiling
>= 0);
330 while (path
->size
> (size_t)ceiling
) {
331 if (path
->ptr
[path
->size
-1] != '/')
334 path
->ptr
[path
->size
-1] = '\0';
339 int git_path_join_unrooted(
340 git_buf
*path_out
, const char *path
, const char *base
, ssize_t
*root_at
)
344 assert(path
&& path_out
);
346 root
= (ssize_t
)git_path_root(path
);
348 if (base
!= NULL
&& root
< 0) {
349 if (git_buf_joinpath(path_out
, base
, path
) < 0)
352 root
= (ssize_t
)strlen(base
);
354 if (git_buf_sets(path_out
, path
) < 0)
360 git_path_equal_or_prefixed(base
, path
, &root
);
369 void git_path_squash_slashes(git_buf
*path
)
376 for (p
= path
->ptr
, q
= path
->ptr
; *q
; p
++, q
++) {
379 while (*q
== '/' && *(q
+1) == '/') {
388 int git_path_prettify(git_buf
*path_out
, const char *path
, const char *base
)
390 char buf
[GIT_PATH_MAX
];
392 assert(path
&& path_out
);
394 /* construct path if needed */
395 if (base
!= NULL
&& git_path_root(path
) < 0) {
396 if (git_buf_joinpath(path_out
, base
, path
) < 0)
398 path
= path_out
->ptr
;
401 if (p_realpath(path
, buf
) == NULL
) {
402 /* git_error_set resets the errno when dealing with a GIT_ERROR_OS kind of error */
403 int error
= (errno
== ENOENT
|| errno
== ENOTDIR
) ? GIT_ENOTFOUND
: -1;
404 git_error_set(GIT_ERROR_OS
, "failed to resolve path '%s'", path
);
406 git_buf_clear(path_out
);
411 return git_buf_sets(path_out
, buf
);
414 int git_path_prettify_dir(git_buf
*path_out
, const char *path
, const char *base
)
416 int error
= git_path_prettify(path_out
, path
, base
);
417 return (error
< 0) ? error
: git_path_to_dir(path_out
);
420 int git_path_to_dir(git_buf
*path
)
422 if (path
->asize
> 0 &&
423 git_buf_len(path
) > 0 &&
424 path
->ptr
[git_buf_len(path
) - 1] != '/')
425 git_buf_putc(path
, '/');
427 return git_buf_oom(path
) ? -1 : 0;
430 void git_path_string_to_dir(char* path
, size_t size
)
432 size_t end
= strlen(path
);
434 if (end
&& path
[end
- 1] != '/' && end
< size
) {
436 path
[end
+ 1] = '\0';
440 int git__percent_decode(git_buf
*decoded_out
, const char *input
)
443 assert(decoded_out
&& input
);
445 len
= (int)strlen(input
);
446 git_buf_clear(decoded_out
);
448 for(i
= 0; i
< len
; i
++)
458 hi
= git__fromhex(input
[i
+ 1]);
459 lo
= git__fromhex(input
[i
+ 2]);
461 if (hi
< 0 || lo
< 0)
464 c
= (char)(hi
<< 4 | lo
);
468 if (git_buf_putc(decoded_out
, c
) < 0)
475 static int error_invalid_local_file_uri(const char *uri
)
477 git_error_set(GIT_ERROR_CONFIG
, "'%s' is not a valid local file URI", uri
);
481 static int local_file_url_prefixlen(const char *file_url
)
485 if (git__prefixcmp(file_url
, "file://") == 0) {
486 if (file_url
[7] == '/')
488 else if (git__prefixcmp(file_url
+ 7, "localhost/") == 0)
495 bool git_path_is_local_file_url(const char *file_url
)
497 return (local_file_url_prefixlen(file_url
) > 0);
500 int git_path_fromurl(git_buf
*local_path_out
, const char *file_url
)
504 assert(local_path_out
&& file_url
);
506 if ((offset
= local_file_url_prefixlen(file_url
)) < 0 ||
507 file_url
[offset
] == '\0' || file_url
[offset
] == '/')
508 return error_invalid_local_file_uri(file_url
);
511 offset
--; /* A *nix absolute path starts with a forward slash */
514 git_buf_clear(local_path_out
);
515 return git__percent_decode(local_path_out
, file_url
+ offset
);
518 int git_path_walk_up(
521 int (*cb
)(void *data
, const char *),
526 ssize_t stop
= 0, scan
;
531 if (ceiling
!= NULL
) {
532 if (git__prefixcmp(path
->ptr
, ceiling
) == 0)
533 stop
= (ssize_t
)strlen(ceiling
);
535 stop
= git_buf_len(path
);
537 scan
= git_buf_len(path
);
539 /* empty path: yield only once */
541 error
= cb(data
, "");
543 git_error_set_after_callback(error
);
547 iter
.ptr
= path
->ptr
;
548 iter
.size
= git_buf_len(path
);
549 iter
.asize
= path
->asize
;
551 while (scan
>= stop
) {
552 error
= cb(data
, iter
.ptr
);
553 iter
.ptr
[scan
] = oldc
;
556 git_error_set_after_callback(error
);
560 scan
= git_buf_rfind_next(&iter
, '/');
563 oldc
= iter
.ptr
[scan
];
565 iter
.ptr
[scan
] = '\0';
570 iter
.ptr
[scan
] = oldc
;
572 /* relative path: yield for the last component */
573 if (!error
&& stop
== 0 && iter
.ptr
[0] != '/') {
574 error
= cb(data
, "");
576 git_error_set_after_callback(error
);
582 bool git_path_exists(const char *path
)
585 return p_access(path
, F_OK
) == 0;
588 bool git_path_isdir(const char *path
)
591 if (p_stat(path
, &st
) < 0)
594 return S_ISDIR(st
.st_mode
) != 0;
597 bool git_path_isfile(const char *path
)
602 if (p_stat(path
, &st
) < 0)
605 return S_ISREG(st
.st_mode
) != 0;
608 bool git_path_islink(const char *path
)
613 if (p_lstat(path
, &st
) < 0)
616 return S_ISLNK(st
.st_mode
) != 0;
621 bool git_path_is_empty_dir(const char *path
)
623 git_win32_path filter_w
;
626 if (git_win32__findfirstfile_filter(filter_w
, path
)) {
627 WIN32_FIND_DATAW findData
;
628 HANDLE hFind
= FindFirstFileW(filter_w
, &findData
);
630 /* FindFirstFile will fail if there are no children to the given
631 * path, which can happen if the given path is a file (and obviously
632 * has no children) or if the given path is an empty mount point.
633 * (Most directories have at least directory entries '.' and '..',
634 * but ridiculously another volume mounted in another drive letter's
635 * path space do not, and thus have nothing to enumerate.) If
636 * FindFirstFile fails, check if this is a directory-like thing
639 if (hFind
== INVALID_HANDLE_VALUE
)
640 return git_path_isdir(path
);
642 /* If the find handle was created successfully, then it's a directory */
646 /* Allow the enumeration to return . and .. and still be considered
647 * empty. In the special case of drive roots (i.e. C:\) where . and
648 * .. do not occur, we can still consider the path to be an empty
649 * directory if there's nothing there. */
650 if (!git_path_is_dot_or_dotdotW(findData
.cFileName
)) {
654 } while (FindNextFileW(hFind
, &findData
));
664 static int path_found_entry(void *payload
, git_buf
*path
)
667 return !git_path_is_dot_or_dotdot(path
->ptr
);
670 bool git_path_is_empty_dir(const char *path
)
673 git_buf dir
= GIT_BUF_INIT
;
675 if (!git_path_isdir(path
))
678 if ((error
= git_buf_sets(&dir
, path
)) != 0)
681 error
= git_path_direach(&dir
, 0, path_found_entry
, NULL
);
683 git_buf_dispose(&dir
);
690 int git_path_set_error(int errno_value
, const char *path
, const char *action
)
692 switch (errno_value
) {
695 git_error_set(GIT_ERROR_OS
, "could not find '%s' to %s", path
, action
);
696 return GIT_ENOTFOUND
;
700 git_error_set(GIT_ERROR_OS
, "invalid path for filesystem '%s'", path
);
701 return GIT_EINVALIDSPEC
;
704 git_error_set(GIT_ERROR_OS
, "failed %s - '%s' already exists", action
, path
);
708 git_error_set(GIT_ERROR_OS
, "failed %s - '%s' is locked", action
, path
);
712 git_error_set(GIT_ERROR_OS
, "could not %s '%s'", action
, path
);
717 int git_path_lstat(const char *path
, struct stat
*st
)
719 if (p_lstat(path
, st
) == 0)
722 return git_path_set_error(errno
, path
, "stat");
725 static bool _check_dir_contents(
728 bool (*predicate
)(const char *))
731 size_t dir_size
= git_buf_len(dir
);
732 size_t sub_size
= strlen(sub
);
735 /* leave base valid even if we could not make space for subdir */
736 if (GIT_ADD_SIZET_OVERFLOW(&alloc_size
, dir_size
, sub_size
) ||
737 GIT_ADD_SIZET_OVERFLOW(&alloc_size
, alloc_size
, 2) ||
738 git_buf_try_grow(dir
, alloc_size
, false) < 0)
742 if (git_buf_joinpath(dir
, dir
->ptr
, sub
) < 0)
745 result
= predicate(dir
->ptr
);
748 git_buf_truncate(dir
, dir_size
);
752 bool git_path_contains(git_buf
*dir
, const char *item
)
754 return _check_dir_contents(dir
, item
, &git_path_exists
);
757 bool git_path_contains_dir(git_buf
*base
, const char *subdir
)
759 return _check_dir_contents(base
, subdir
, &git_path_isdir
);
762 bool git_path_contains_file(git_buf
*base
, const char *file
)
764 return _check_dir_contents(base
, file
, &git_path_isfile
);
767 int git_path_find_dir(git_buf
*dir
, const char *path
, const char *base
)
769 int error
= git_path_join_unrooted(dir
, path
, base
, NULL
);
772 char buf
[GIT_PATH_MAX
];
773 if (p_realpath(dir
->ptr
, buf
) != NULL
)
774 error
= git_buf_sets(dir
, buf
);
777 /* call dirname if this is not a directory */
778 if (!error
) /* && git_path_isdir(dir->ptr) == false) */
779 error
= (git_path_dirname_r(dir
, dir
->ptr
) < 0) ? -1 : 0;
782 error
= git_path_to_dir(dir
);
787 int git_path_resolve_relative(git_buf
*path
, size_t ceiling
)
789 char *base
, *to
, *from
, *next
;
792 GIT_ERROR_CHECK_ALLOC_BUF(path
);
794 if (ceiling
> path
->size
)
795 ceiling
= path
->size
;
797 /* recognize drive prefixes, etc. that should not be backed over */
799 ceiling
= git_path_root(path
->ptr
) + 1;
801 /* recognize URL prefixes that should not be backed over */
803 for (next
= path
->ptr
; *next
&& git__isalpha(*next
); ++next
);
804 if (next
[0] == ':' && next
[1] == '/' && next
[2] == '/')
805 ceiling
= (next
+ 3) - path
->ptr
;
808 base
= to
= from
= path
->ptr
+ ceiling
;
811 for (next
= from
; *next
&& *next
!= '/'; ++next
);
815 if (len
== 1 && from
[0] == '.')
816 /* do nothing with singleton dot */;
818 else if (len
== 2 && from
[0] == '.' && from
[1] == '.') {
819 /* error out if trying to up one from a hard base */
820 if (to
== base
&& ceiling
!= 0) {
821 git_error_set(GIT_ERROR_INVALID
,
822 "cannot strip root component off url");
826 /* no more path segments to strip,
827 * use '../' as a new base path */
833 memmove(to
, from
, len
);
836 /* this is now the base, can't back up from a
840 /* back up a path segment */
841 while (to
> base
&& to
[-1] == '/') to
--;
842 while (to
> base
&& to
[-1] != '/') to
--;
845 if (*next
== '/' && *from
!= '/')
849 memmove(to
, from
, len
);
856 while (*from
== '/') from
++;
861 path
->size
= to
- path
->ptr
;
866 int git_path_apply_relative(git_buf
*target
, const char *relpath
)
868 return git_buf_joinpath(target
, git_buf_cstr(target
), relpath
) ||
869 git_path_resolve_relative(target
, 0);
873 const char *name1
, size_t len1
, int isdir1
,
874 const char *name2
, size_t len2
, int isdir2
,
875 int (*compare
)(const char *, const char *, size_t))
877 unsigned char c1
, c2
;
878 size_t len
= len1
< len2
? len1
: len2
;
881 cmp
= compare(name1
, name2
, len
);
888 if (c1
== '\0' && isdir1
)
891 if (c2
== '\0' && isdir2
)
894 return (c1
< c2
) ? -1 : (c1
> c2
) ? 1 : 0;
897 size_t git_path_common_dirlen(const char *one
, const char *two
)
899 const char *p
, *q
, *dirsep
= NULL
;
901 for (p
= one
, q
= two
; *p
&& *q
; p
++, q
++) {
902 if (*p
== '/' && *q
== '/')
908 return dirsep
? (dirsep
- one
) + 1 : 0;
911 int git_path_make_relative(git_buf
*path
, const char *parent
)
913 const char *p
, *q
, *p_dirsep
, *q_dirsep
;
914 size_t plen
= path
->size
, newlen
, alloclen
, depth
= 1, i
, offset
;
916 for (p_dirsep
= p
= path
->ptr
, q_dirsep
= q
= parent
; *p
&& *q
; p
++, q
++) {
917 if (*p
== '/' && *q
== '/') {
925 /* need at least 1 common path segment */
926 if ((p_dirsep
== path
->ptr
|| q_dirsep
== parent
) &&
927 (*p_dirsep
!= '/' || *q_dirsep
!= '/')) {
928 git_error_set(GIT_ERROR_INVALID
,
929 "%s is not a parent of %s", parent
, path
->ptr
);
930 return GIT_ENOTFOUND
;
933 if (*p
== '/' && !*q
)
935 else if (!*p
&& *q
== '/')
938 return git_buf_clear(path
), 0;
944 plen
-= (p
- path
->ptr
);
947 return git_buf_set(path
, p
, plen
);
949 for (; (q
= strchr(q
, '/')) && *(q
+ 1); q
++)
952 GIT_ERROR_CHECK_ALLOC_MULTIPLY(&newlen
, depth
, 3);
953 GIT_ERROR_CHECK_ALLOC_ADD(&newlen
, newlen
, plen
);
955 GIT_ERROR_CHECK_ALLOC_ADD(&alloclen
, newlen
, 1);
957 /* save the offset as we might realllocate the pointer */
958 offset
= p
- path
->ptr
;
959 if (git_buf_try_grow(path
, alloclen
, 1) < 0)
961 p
= path
->ptr
+ offset
;
963 memmove(path
->ptr
+ (depth
* 3), p
, plen
+ 1);
965 for (i
= 0; i
< depth
; i
++)
966 memcpy(path
->ptr
+ (i
* 3), "../", 3);
972 bool git_path_has_non_ascii(const char *path
, size_t pathlen
)
974 const uint8_t *scan
= (const uint8_t *)path
, *end
;
976 for (end
= scan
+ pathlen
; scan
< end
; ++scan
)
985 int git_path_iconv_init_precompose(git_path_iconv_t
*ic
)
987 git_buf_init(&ic
->buf
, 0);
988 ic
->map
= iconv_open(GIT_PATH_REPO_ENCODING
, GIT_PATH_NATIVE_ENCODING
);
992 void git_path_iconv_clear(git_path_iconv_t
*ic
)
995 if (ic
->map
!= (iconv_t
)-1)
996 iconv_close(ic
->map
);
997 git_buf_dispose(&ic
->buf
);
1001 int git_path_iconv(git_path_iconv_t
*ic
, const char **in
, size_t *inlen
)
1003 char *nfd
= (char*)*in
, *nfc
;
1004 size_t nfdlen
= *inlen
, nfclen
, wantlen
= nfdlen
, alloclen
, rv
;
1007 if (!ic
|| ic
->map
== (iconv_t
)-1 ||
1008 !git_path_has_non_ascii(*in
, *inlen
))
1011 git_buf_clear(&ic
->buf
);
1014 GIT_ERROR_CHECK_ALLOC_ADD(&alloclen
, wantlen
, 1);
1015 if (git_buf_grow(&ic
->buf
, alloclen
) < 0)
1018 nfc
= ic
->buf
.ptr
+ ic
->buf
.size
;
1019 nfclen
= ic
->buf
.asize
- ic
->buf
.size
;
1021 rv
= iconv(ic
->map
, &nfd
, &nfdlen
, &nfc
, &nfclen
);
1023 ic
->buf
.size
= (nfc
- ic
->buf
.ptr
);
1025 if (rv
!= (size_t)-1)
1028 /* if we cannot convert the data (probably because iconv thinks
1029 * it is not valid UTF-8 source data), then use original data
1034 /* make space for 2x the remaining data to be converted
1035 * (with per retry overhead to avoid infinite loops)
1037 wantlen
= ic
->buf
.size
+ max(nfclen
, nfdlen
) * 2 + (size_t)(retry
* 4);
1043 ic
->buf
.ptr
[ic
->buf
.size
] = '\0';
1046 *inlen
= ic
->buf
.size
;
1051 git_error_set(GIT_ERROR_OS
, "unable to convert unicode path data");
1055 static const char *nfc_file
= "\xC3\x85\x73\x74\x72\xC3\xB6\x6D.XXXXXX";
1056 static const char *nfd_file
= "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D.XXXXXX";
1058 /* Check if the platform is decomposing unicode data for us. We will
1059 * emulate core Git and prefer to use precomposed unicode data internally
1060 * on these platforms, composing the decomposed unicode on the fly.
1062 * This mainly happens on the Mac where HDFS stores filenames as
1063 * decomposed unicode. Even on VFAT and SAMBA file systems, the Mac will
1064 * return decomposed unicode from readdir() even when the actual
1065 * filesystem is storing precomposed unicode.
1067 bool git_path_does_fs_decompose_unicode(const char *root
)
1069 git_buf path
= GIT_BUF_INIT
;
1071 bool found_decomposed
= false;
1074 /* Create a file using a precomposed path and then try to find it
1075 * using the decomposed name. If the lookup fails, then we will mark
1076 * that we should precompose unicode for this repository.
1078 if (git_buf_joinpath(&path
, root
, nfc_file
) < 0 ||
1079 (fd
= p_mkstemp(path
.ptr
)) < 0)
1083 /* record trailing digits generated by mkstemp */
1084 memcpy(tmp
, path
.ptr
+ path
.size
- sizeof(tmp
), sizeof(tmp
));
1086 /* try to look up as NFD path */
1087 if (git_buf_joinpath(&path
, root
, nfd_file
) < 0)
1089 memcpy(path
.ptr
+ path
.size
- sizeof(tmp
), tmp
, sizeof(tmp
));
1091 found_decomposed
= git_path_exists(path
.ptr
);
1093 /* remove temporary file (using original precomposed path) */
1094 if (git_buf_joinpath(&path
, root
, nfc_file
) < 0)
1096 memcpy(path
.ptr
+ path
.size
- sizeof(tmp
), tmp
, sizeof(tmp
));
1098 (void)p_unlink(path
.ptr
);
1101 git_buf_dispose(&path
);
1102 return found_decomposed
;
1107 bool git_path_does_fs_decompose_unicode(const char *root
)
1115 #if defined(__sun) || defined(__GNU__)
1116 typedef char path_dirent_data
[sizeof(struct dirent
) + FILENAME_MAX
+ 1];
1118 typedef struct dirent path_dirent_data
;
1121 int git_path_direach(
1124 int (*fn
)(void *, git_buf
*),
1132 #ifdef GIT_USE_ICONV
1133 git_path_iconv_t ic
= GIT_PATH_ICONV_INIT
;
1138 if (git_path_to_dir(path
) < 0)
1141 wd_len
= git_buf_len(path
);
1143 if ((dir
= opendir(path
->ptr
)) == NULL
) {
1144 git_error_set(GIT_ERROR_OS
, "failed to open directory '%s'", path
->ptr
);
1145 if (errno
== ENOENT
)
1146 return GIT_ENOTFOUND
;
1151 #ifdef GIT_USE_ICONV
1152 if ((flags
& GIT_PATH_DIR_PRECOMPOSE_UNICODE
) != 0)
1153 (void)git_path_iconv_init_precompose(&ic
);
1156 while ((de
= readdir(dir
)) != NULL
) {
1157 const char *de_path
= de
->d_name
;
1158 size_t de_len
= strlen(de_path
);
1160 if (git_path_is_dot_or_dotdot(de_path
))
1163 #ifdef GIT_USE_ICONV
1164 if ((error
= git_path_iconv(&ic
, &de_path
, &de_len
)) < 0)
1168 if ((error
= git_buf_put(path
, de_path
, de_len
)) < 0)
1172 error
= fn(arg
, path
);
1174 git_buf_truncate(path
, wd_len
); /* restore path */
1176 /* Only set our own error if the callback did not set one already */
1178 if (!git_error_last())
1179 git_error_set_after_callback(error
);
1187 #ifdef GIT_USE_ICONV
1188 git_path_iconv_clear(&ic
);
1194 #if defined(GIT_WIN32) && !defined(__MINGW32__)
1196 /* Using _FIND_FIRST_EX_LARGE_FETCH may increase performance in Windows 7
1199 #ifndef FIND_FIRST_EX_LARGE_FETCH
1200 # define FIND_FIRST_EX_LARGE_FETCH 2
1203 int git_path_diriter_init(
1204 git_path_diriter
*diriter
,
1208 git_win32_path path_filter
;
1210 static int is_win7_or_later
= -1;
1211 if (is_win7_or_later
< 0)
1212 is_win7_or_later
= git_has_win32_version(6, 1, 0);
1214 assert(diriter
&& path
);
1216 memset(diriter
, 0, sizeof(git_path_diriter
));
1217 diriter
->handle
= INVALID_HANDLE_VALUE
;
1219 if (git_buf_puts(&diriter
->path_utf8
, path
) < 0)
1222 path_trim_slashes(&diriter
->path_utf8
);
1224 if (diriter
->path_utf8
.size
== 0) {
1225 git_error_set(GIT_ERROR_FILESYSTEM
, "could not open directory '%s'", path
);
1229 if ((diriter
->parent_len
= git_win32_path_from_utf8(diriter
->path
, diriter
->path_utf8
.ptr
)) < 0 ||
1230 !git_win32__findfirstfile_filter(path_filter
, diriter
->path_utf8
.ptr
)) {
1231 git_error_set(GIT_ERROR_OS
, "could not parse the directory path '%s'", path
);
1235 diriter
->handle
= FindFirstFileExW(
1237 is_win7_or_later
? FindExInfoBasic
: FindExInfoStandard
,
1239 FindExSearchNameMatch
,
1241 is_win7_or_later
? FIND_FIRST_EX_LARGE_FETCH
: 0);
1243 if (diriter
->handle
== INVALID_HANDLE_VALUE
) {
1244 git_error_set(GIT_ERROR_OS
, "could not open directory '%s'", path
);
1248 diriter
->parent_utf8_len
= diriter
->path_utf8
.size
;
1249 diriter
->flags
= flags
;
1253 static int diriter_update_paths(git_path_diriter
*diriter
)
1255 size_t filename_len
, path_len
;
1257 filename_len
= wcslen(diriter
->current
.cFileName
);
1259 if (GIT_ADD_SIZET_OVERFLOW(&path_len
, diriter
->parent_len
, filename_len
) ||
1260 GIT_ADD_SIZET_OVERFLOW(&path_len
, path_len
, 2))
1263 if (path_len
> GIT_WIN_PATH_UTF16
) {
1264 git_error_set(GIT_ERROR_FILESYSTEM
,
1265 "invalid path '%.*ls\\%ls' (path too long)",
1266 diriter
->parent_len
, diriter
->path
, diriter
->current
.cFileName
);
1270 diriter
->path
[diriter
->parent_len
] = L
'\\';
1271 memcpy(&diriter
->path
[diriter
->parent_len
+1],
1272 diriter
->current
.cFileName
, filename_len
* sizeof(wchar_t));
1273 diriter
->path
[path_len
-1] = L
'\0';
1275 git_buf_truncate(&diriter
->path_utf8
, diriter
->parent_utf8_len
);
1277 if (diriter
->parent_utf8_len
> 0 &&
1278 diriter
->path_utf8
.ptr
[diriter
->parent_utf8_len
-1] != '/')
1279 git_buf_putc(&diriter
->path_utf8
, '/');
1281 git_buf_put_w(&diriter
->path_utf8
, diriter
->current
.cFileName
, filename_len
);
1283 if (git_buf_oom(&diriter
->path_utf8
))
1289 int git_path_diriter_next(git_path_diriter
*diriter
)
1291 bool skip_dot
= !(diriter
->flags
& GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT
);
1294 /* Our first time through, we already have the data from
1295 * FindFirstFileW. Use it, otherwise get the next file.
1297 if (!diriter
->needs_next
)
1298 diriter
->needs_next
= 1;
1299 else if (!FindNextFileW(diriter
->handle
, &diriter
->current
))
1300 return GIT_ITEROVER
;
1301 } while (skip_dot
&& git_path_is_dot_or_dotdotW(diriter
->current
.cFileName
));
1303 if (diriter_update_paths(diriter
) < 0)
1309 int git_path_diriter_filename(
1312 git_path_diriter
*diriter
)
1314 assert(out
&& out_len
&& diriter
);
1316 assert(diriter
->path_utf8
.size
> diriter
->parent_utf8_len
);
1318 *out
= &diriter
->path_utf8
.ptr
[diriter
->parent_utf8_len
+1];
1319 *out_len
= diriter
->path_utf8
.size
- diriter
->parent_utf8_len
- 1;
1323 int git_path_diriter_fullpath(
1326 git_path_diriter
*diriter
)
1328 assert(out
&& out_len
&& diriter
);
1330 *out
= diriter
->path_utf8
.ptr
;
1331 *out_len
= diriter
->path_utf8
.size
;
1335 int git_path_diriter_stat(struct stat
*out
, git_path_diriter
*diriter
)
1337 assert(out
&& diriter
);
1339 return git_win32__file_attribute_to_stat(out
,
1340 (WIN32_FILE_ATTRIBUTE_DATA
*)&diriter
->current
,
1344 void git_path_diriter_free(git_path_diriter
*diriter
)
1346 if (diriter
== NULL
)
1349 git_buf_dispose(&diriter
->path_utf8
);
1351 if (diriter
->handle
!= INVALID_HANDLE_VALUE
) {
1352 FindClose(diriter
->handle
);
1353 diriter
->handle
= INVALID_HANDLE_VALUE
;
1359 int git_path_diriter_init(
1360 git_path_diriter
*diriter
,
1364 assert(diriter
&& path
);
1366 memset(diriter
, 0, sizeof(git_path_diriter
));
1368 if (git_buf_puts(&diriter
->path
, path
) < 0)
1371 path_trim_slashes(&diriter
->path
);
1373 if (diriter
->path
.size
== 0) {
1374 git_error_set(GIT_ERROR_FILESYSTEM
, "could not open directory '%s'", path
);
1378 if ((diriter
->dir
= opendir(diriter
->path
.ptr
)) == NULL
) {
1379 git_buf_dispose(&diriter
->path
);
1381 git_error_set(GIT_ERROR_OS
, "failed to open directory '%s'", path
);
1385 #ifdef GIT_USE_ICONV
1386 if ((flags
& GIT_PATH_DIR_PRECOMPOSE_UNICODE
) != 0)
1387 (void)git_path_iconv_init_precompose(&diriter
->ic
);
1390 diriter
->parent_len
= diriter
->path
.size
;
1391 diriter
->flags
= flags
;
1396 int git_path_diriter_next(git_path_diriter
*diriter
)
1399 const char *filename
;
1400 size_t filename_len
;
1401 bool skip_dot
= !(diriter
->flags
& GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT
);
1409 if ((de
= readdir(diriter
->dir
)) == NULL
) {
1411 return GIT_ITEROVER
;
1413 git_error_set(GIT_ERROR_OS
,
1414 "could not read directory '%s'", diriter
->path
.ptr
);
1417 } while (skip_dot
&& git_path_is_dot_or_dotdot(de
->d_name
));
1419 filename
= de
->d_name
;
1420 filename_len
= strlen(filename
);
1422 #ifdef GIT_USE_ICONV
1423 if ((diriter
->flags
& GIT_PATH_DIR_PRECOMPOSE_UNICODE
) != 0 &&
1424 (error
= git_path_iconv(&diriter
->ic
, &filename
, &filename_len
)) < 0)
1428 git_buf_truncate(&diriter
->path
, diriter
->parent_len
);
1430 if (diriter
->parent_len
> 0 &&
1431 diriter
->path
.ptr
[diriter
->parent_len
-1] != '/')
1432 git_buf_putc(&diriter
->path
, '/');
1434 git_buf_put(&diriter
->path
, filename
, filename_len
);
1436 if (git_buf_oom(&diriter
->path
))
1442 int git_path_diriter_filename(
1445 git_path_diriter
*diriter
)
1447 assert(out
&& out_len
&& diriter
);
1449 assert(diriter
->path
.size
> diriter
->parent_len
);
1451 *out
= &diriter
->path
.ptr
[diriter
->parent_len
+1];
1452 *out_len
= diriter
->path
.size
- diriter
->parent_len
- 1;
1456 int git_path_diriter_fullpath(
1459 git_path_diriter
*diriter
)
1461 assert(out
&& out_len
&& diriter
);
1463 *out
= diriter
->path
.ptr
;
1464 *out_len
= diriter
->path
.size
;
1468 int git_path_diriter_stat(struct stat
*out
, git_path_diriter
*diriter
)
1470 assert(out
&& diriter
);
1472 return git_path_lstat(diriter
->path
.ptr
, out
);
1475 void git_path_diriter_free(git_path_diriter
*diriter
)
1477 if (diriter
== NULL
)
1481 closedir(diriter
->dir
);
1482 diriter
->dir
= NULL
;
1485 #ifdef GIT_USE_ICONV
1486 git_path_iconv_clear(&diriter
->ic
);
1489 git_buf_dispose(&diriter
->path
);
1494 int git_path_dirload(
1495 git_vector
*contents
,
1500 git_path_diriter iter
= GIT_PATH_DIRITER_INIT
;
1506 assert(contents
&& path
);
1508 if ((error
= git_path_diriter_init(&iter
, path
, flags
)) < 0)
1511 while ((error
= git_path_diriter_next(&iter
)) == 0) {
1512 if ((error
= git_path_diriter_fullpath(&name
, &name_len
, &iter
)) < 0)
1515 assert(name_len
> prefix_len
);
1517 dup
= git__strndup(name
+ prefix_len
, name_len
- prefix_len
);
1518 GIT_ERROR_CHECK_ALLOC(dup
);
1520 if ((error
= git_vector_insert(contents
, dup
)) < 0)
1524 if (error
== GIT_ITEROVER
)
1527 git_path_diriter_free(&iter
);
1531 int git_path_from_url_or_path(git_buf
*local_path_out
, const char *url_or_path
)
1533 if (git_path_is_local_file_url(url_or_path
))
1534 return git_path_fromurl(local_path_out
, url_or_path
);
1536 return git_buf_sets(local_path_out
, url_or_path
);
1539 /* Reject paths like AUX or COM1, or those versions that end in a dot or
1540 * colon. ("AUX." or "AUX:")
1542 GIT_INLINE(bool) verify_dospath(
1543 const char *component
,
1545 const char dospath
[3],
1548 size_t last
= trailing_num
? 4 : 3;
1550 if (len
< last
|| git__strncasecmp(component
, dospath
, 3) != 0)
1553 if (trailing_num
&& (component
[3] < '1' || component
[3] > '9'))
1556 return (len
> last
&&
1557 component
[last
] != '.' &&
1558 component
[last
] != ':');
1561 static int32_t next_hfs_char(const char **in
, size_t *len
)
1565 int cp_len
= git__utf8_iterate((const uint8_t *)(*in
), (int)(*len
), &codepoint
);
1572 /* these code points are ignored completely */
1573 switch (codepoint
) {
1574 case 0x200c: /* ZERO WIDTH NON-JOINER */
1575 case 0x200d: /* ZERO WIDTH JOINER */
1576 case 0x200e: /* LEFT-TO-RIGHT MARK */
1577 case 0x200f: /* RIGHT-TO-LEFT MARK */
1578 case 0x202a: /* LEFT-TO-RIGHT EMBEDDING */
1579 case 0x202b: /* RIGHT-TO-LEFT EMBEDDING */
1580 case 0x202c: /* POP DIRECTIONAL FORMATTING */
1581 case 0x202d: /* LEFT-TO-RIGHT OVERRIDE */
1582 case 0x202e: /* RIGHT-TO-LEFT OVERRIDE */
1583 case 0x206a: /* INHIBIT SYMMETRIC SWAPPING */
1584 case 0x206b: /* ACTIVATE SYMMETRIC SWAPPING */
1585 case 0x206c: /* INHIBIT ARABIC FORM SHAPING */
1586 case 0x206d: /* ACTIVATE ARABIC FORM SHAPING */
1587 case 0x206e: /* NATIONAL DIGIT SHAPES */
1588 case 0x206f: /* NOMINAL DIGIT SHAPES */
1589 case 0xfeff: /* ZERO WIDTH NO-BREAK SPACE */
1593 /* fold into lowercase -- this will only fold characters in
1594 * the ASCII range, which is perfectly fine, because the
1595 * git folder name can only be composed of ascii characters
1597 return git__tolower(codepoint
);
1599 return 0; /* NULL byte -- end of string */
1602 static bool verify_dotgit_hfs_generic(const char *path
, size_t len
, const char *needle
, size_t needle_len
)
1607 if (next_hfs_char(&path
, &len
) != '.')
1610 for (i
= 0; i
< needle_len
; i
++) {
1611 c
= next_hfs_char(&path
, &len
);
1616 if (next_hfs_char(&path
, &len
) != '\0')
1622 static bool verify_dotgit_hfs(const char *path
, size_t len
)
1624 return verify_dotgit_hfs_generic(path
, len
, "git", CONST_STRLEN("git"));
1627 GIT_INLINE(bool) verify_dotgit_ntfs(git_repository
*repo
, const char *path
, size_t len
)
1629 git_buf
*reserved
= git_repository__reserved_names_win32
;
1630 size_t reserved_len
= git_repository__reserved_names_win32_len
;
1631 size_t start
= 0, i
;
1634 git_repository__reserved_names(&reserved
, &reserved_len
, repo
, true);
1636 for (i
= 0; i
< reserved_len
; i
++) {
1637 git_buf
*r
= &reserved
[i
];
1639 if (len
>= r
->size
&&
1640 strncasecmp(path
, r
->ptr
, r
->size
) == 0) {
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.
1654 if (path
[start
] == '\\' || path
[start
] == ':')
1657 /* Reject paths like '.git ' or '.git.' */
1658 for (i
= start
; i
< len
; i
++) {
1659 if (path
[i
] != ' ' && path
[i
] != '.')
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.)
1675 GIT_INLINE(bool) ntfs_end_of_filename(const char *path
)
1677 const char *c
= path
;
1680 if (*c
== '\0' || *c
== ':')
1682 if (*c
!= ' ' && *c
!= '.')
1689 GIT_INLINE(bool) verify_dotgit_ntfs_generic(const char *name
, size_t len
, const char *dotgit_name
, size_t dotgit_len
, const char *shortname_pfix
)
1693 if (name
[0] == '.' && len
>= dotgit_len
&&
1694 !strncasecmp(name
+ 1, dotgit_name
, dotgit_len
)) {
1695 return !ntfs_end_of_filename(name
+ dotgit_len
+ 1);
1698 /* Detect the basic NTFS shortname with the first six chars */
1699 if (!strncasecmp(name
, dotgit_name
, 6) && name
[6] == '~' &&
1700 name
[7] >= '1' && name
[7] <= '4')
1701 return !ntfs_end_of_filename(name
+ 8);
1703 /* Catch fallback names */
1704 for (i
= 0, saw_tilde
= 0; i
< 8; i
++) {
1705 if (name
[i
] == '\0') {
1707 } else if (saw_tilde
) {
1708 if (name
[i
] < '0' || name
[i
] > '9')
1710 } else if (name
[i
] == '~') {
1711 if (name
[i
+1] < '1' || name
[i
+1] > '9')
1714 } else if (i
>= 6) {
1716 } else if ((unsigned char)name
[i
] > 127) {
1718 } else if (git__tolower(name
[i
]) != shortname_pfix
[i
]) {
1723 return !ntfs_end_of_filename(name
+ i
);
1726 GIT_INLINE(bool) verify_char(unsigned char c
, unsigned int flags
)
1728 if ((flags
& GIT_PATH_REJECT_BACKSLASH
) && c
== '\\')
1731 if ((flags
& GIT_PATH_REJECT_SLASH
) && c
== '/')
1734 if (flags
& GIT_PATH_REJECT_NT_CHARS
) {
1754 * Return the length of the common prefix between str and prefix, comparing them
1755 * case-insensitively (must be ASCII to match).
1757 GIT_INLINE(size_t) common_prefix_icase(const char *str
, size_t len
, const char *prefix
)
1761 while (len
>0 && tolower(*str
) == tolower(*prefix
)) {
1772 * We fundamentally don't like some paths when dealing with user-inputted
1773 * strings (in checkout or ref names): we don't want dot or dot-dot
1774 * anywhere, we want to avoid writing weird paths on Windows that can't
1775 * be handled by tools that use the non-\\?\ APIs, we don't want slashes
1776 * or double slashes at the end of paths that can make them ambiguous.
1778 * For checkout, we don't want to recurse into ".git" either.
1780 static bool verify_component(
1781 git_repository
*repo
,
1782 const char *component
,
1790 if ((flags
& GIT_PATH_REJECT_TRAVERSAL
) &&
1791 len
== 1 && component
[0] == '.')
1794 if ((flags
& GIT_PATH_REJECT_TRAVERSAL
) &&
1795 len
== 2 && component
[0] == '.' && component
[1] == '.')
1798 if ((flags
& GIT_PATH_REJECT_TRAILING_DOT
) && component
[len
-1] == '.')
1801 if ((flags
& GIT_PATH_REJECT_TRAILING_SPACE
) && component
[len
-1] == ' ')
1804 if ((flags
& GIT_PATH_REJECT_TRAILING_COLON
) && component
[len
-1] == ':')
1807 if (flags
& GIT_PATH_REJECT_DOS_PATHS
) {
1808 if (!verify_dospath(component
, len
, "CON", false) ||
1809 !verify_dospath(component
, len
, "PRN", false) ||
1810 !verify_dospath(component
, len
, "AUX", false) ||
1811 !verify_dospath(component
, len
, "NUL", false) ||
1812 !verify_dospath(component
, len
, "COM", true) ||
1813 !verify_dospath(component
, len
, "LPT", true))
1817 if (flags
& GIT_PATH_REJECT_DOT_GIT_HFS
) {
1818 if (!verify_dotgit_hfs(component
, len
))
1820 if (S_ISLNK(mode
) && git_path_is_gitfile(component
, len
, GIT_PATH_GITFILE_GITMODULES
, GIT_PATH_FS_HFS
))
1824 if (flags
& GIT_PATH_REJECT_DOT_GIT_NTFS
) {
1825 if (!verify_dotgit_ntfs(repo
, component
, len
))
1827 if (S_ISLNK(mode
) && git_path_is_gitfile(component
, len
, GIT_PATH_GITFILE_GITMODULES
, GIT_PATH_FS_NTFS
))
1831 /* don't bother rerunning the `.git` test if we ran the HFS or NTFS
1832 * specific tests, they would have already rejected `.git`.
1834 if ((flags
& GIT_PATH_REJECT_DOT_GIT_HFS
) == 0 &&
1835 (flags
& GIT_PATH_REJECT_DOT_GIT_NTFS
) == 0 &&
1836 (flags
& GIT_PATH_REJECT_DOT_GIT_LITERAL
)) {
1838 component
[0] == '.' &&
1839 (component
[1] == 'g' || component
[1] == 'G') &&
1840 (component
[2] == 'i' || component
[2] == 'I') &&
1841 (component
[3] == 't' || component
[3] == 'T')) {
1845 if (S_ISLNK(mode
) && common_prefix_icase(component
, len
, ".gitmodules") == len
)
1853 GIT_INLINE(unsigned int) dotgit_flags(
1854 git_repository
*repo
,
1857 int protectHFS
= 0, protectNTFS
= 1;
1860 flags
|= GIT_PATH_REJECT_DOT_GIT_LITERAL
;
1866 if (repo
&& !protectHFS
)
1867 error
= git_repository__configmap_lookup(&protectHFS
, repo
, GIT_CONFIGMAP_PROTECTHFS
);
1868 if (!error
&& protectHFS
)
1869 flags
|= GIT_PATH_REJECT_DOT_GIT_HFS
;
1872 error
= git_repository__configmap_lookup(&protectNTFS
, repo
, GIT_CONFIGMAP_PROTECTNTFS
);
1873 if (!error
&& protectNTFS
)
1874 flags
|= GIT_PATH_REJECT_DOT_GIT_NTFS
;
1879 bool git_path_isvalid(
1880 git_repository
*repo
,
1885 const char *start
, *c
;
1887 /* Upgrade the ".git" checks based on platform */
1888 if ((flags
& GIT_PATH_REJECT_DOT_GIT
))
1889 flags
= dotgit_flags(repo
, flags
);
1891 for (start
= c
= path
; *c
; c
++) {
1892 if (!verify_char(*c
, flags
))
1896 if (!verify_component(repo
, start
, (c
- start
), mode
, flags
))
1903 return verify_component(repo
, start
, (c
- start
), mode
, flags
);
1906 int git_path_normalize_slashes(git_buf
*out
, const char *path
)
1911 if ((error
= git_buf_puts(out
, path
)) < 0)
1914 for (p
= out
->ptr
; *p
; p
++) {
1922 static const struct {
1927 { "gitignore", "gi250a", CONST_STRLEN("gitignore") },
1928 { "gitmodules", "gi7eba", CONST_STRLEN("gitmodules") },
1929 { "gitattributes", "gi7d29", CONST_STRLEN("gitattributes") }
1932 extern int git_path_is_gitfile(const char *path
, size_t pathlen
, git_path_gitfile gitfile
, git_path_fs fs
)
1934 const char *file
, *hash
;
1937 if (!(gitfile
>= GIT_PATH_GITFILE_GITIGNORE
&& gitfile
< ARRAY_SIZE(gitfiles
))) {
1938 git_error_set(GIT_ERROR_OS
, "invalid gitfile for path validation");
1942 file
= gitfiles
[gitfile
].file
;
1943 filelen
= gitfiles
[gitfile
].filelen
;
1944 hash
= gitfiles
[gitfile
].hash
;
1947 case GIT_PATH_FS_GENERIC
:
1948 return !verify_dotgit_ntfs_generic(path
, pathlen
, file
, filelen
, hash
) ||
1949 !verify_dotgit_hfs_generic(path
, pathlen
, file
, filelen
);
1950 case GIT_PATH_FS_NTFS
:
1951 return !verify_dotgit_ntfs_generic(path
, pathlen
, file
, filelen
, hash
);
1952 case GIT_PATH_FS_HFS
:
1953 return !verify_dotgit_hfs_generic(path
, pathlen
, file
, filelen
);
1955 git_error_set(GIT_ERROR_OS
, "invalid filesystem for path validation");
1960 bool git_path_supports_symlinks(const char *dir
)
1962 git_buf path
= GIT_BUF_INIT
;
1963 bool supported
= false;
1967 if ((fd
= git_futils_mktmp(&path
, dir
, 0666)) < 0 ||
1969 p_unlink(path
.ptr
) < 0 ||
1970 p_symlink("testing", path
.ptr
) < 0 ||
1971 p_lstat(path
.ptr
, &st
) < 0)
1974 supported
= (S_ISLNK(st
.st_mode
) != 0);
1977 (void)p_unlink(path
.ptr
);
1978 git_buf_dispose(&path
);
1982 int git_path_validate_system_file_ownership(const char *path
)
1990 PSECURITY_DESCRIPTOR descriptor
= NULL
;
1992 TOKEN_USER
*info
= NULL
;
1996 if (git_win32_path_from_utf8(buf
, path
) < 0)
1999 err
= GetNamedSecurityInfoW(buf
, SE_FILE_OBJECT
,
2000 OWNER_SECURITY_INFORMATION
|
2001 DACL_SECURITY_INFORMATION
,
2002 &owner_sid
, NULL
, NULL
, NULL
, &descriptor
);
2004 if (err
== ERROR_FILE_NOT_FOUND
|| err
== ERROR_PATH_NOT_FOUND
) {
2005 ret
= GIT_ENOTFOUND
;
2009 if (err
!= ERROR_SUCCESS
) {
2010 git_error_set(GIT_ERROR_OS
, "failed to get security information");
2015 if (!IsValidSid(owner_sid
)) {
2016 git_error_set(GIT_ERROR_INVALID
, "programdata configuration file owner is unknown");
2021 if (IsWellKnownSid(owner_sid
, WinBuiltinAdministratorsSid
) ||
2022 IsWellKnownSid(owner_sid
, WinLocalSystemSid
)) {
2027 /* Obtain current user's SID */
2028 if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY
, &token
) &&
2029 !GetTokenInformation(token
, TokenUser
, NULL
, 0, &len
)) {
2030 info
= git__malloc(len
);
2031 GIT_ERROR_CHECK_ALLOC(info
);
2032 if (!GetTokenInformation(token
, TokenUser
, info
, len
, &len
)) {
2039 * If the file is owned by the same account that is running the current
2040 * process, it's okay to read from that file.
2042 if (info
&& EqualSid(owner_sid
, info
->User
.Sid
))
2045 git_error_set(GIT_ERROR_INVALID
, "programdata configuration file owner is not valid");
2052 LocalFree(descriptor
);