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.
10 #include "repository.h"
12 #include "win32/posix.h"
13 #include "win32/w32_buffer.h"
14 #include "win32/w32_util.h"
15 #include "win32/version.h"
22 #define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':')
25 static bool looks_like_network_computer_name(const char *path
, int pos
)
30 if (path
[0] != '/' || path
[1] != '/')
43 * Based on the Android implementation, BSD licensed.
44 * http://android.git.kernel.org/
46 * Copyright (C) 2008 The Android Open Source Project
47 * All rights reserved.
49 * Redistribution and use in source and binary forms, with or without
50 * modification, are permitted provided that the following conditions
52 * * Redistributions of source code must retain the above copyright
53 * notice, this list of conditions and the following disclaimer.
54 * * Redistributions in binary form must reproduce the above copyright
55 * notice, this list of conditions and the following disclaimer in
56 * the documentation and/or other materials provided with the
59 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
60 * AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
61 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
62 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
63 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
64 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
65 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
66 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
67 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
68 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
69 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
72 int git_path_basename_r(git_buf
*buffer
, const char *path
)
74 const char *endp
, *startp
;
77 /* Empty or NULL string gets treated as "." */
78 if (path
== NULL
|| *path
== '\0') {
84 /* Strip trailing slashes */
85 endp
= path
+ strlen(path
) - 1;
86 while (endp
> path
&& *endp
== '/')
89 /* All slashes becomes "/" */
90 if (endp
== path
&& *endp
== '/') {
96 /* Find the start of the base */
98 while (startp
> path
&& *(startp
- 1) != '/')
101 /* Cast is safe because max path < max int */
102 len
= (int)(endp
- startp
+ 1);
107 if (buffer
!= NULL
&& git_buf_set(buffer
, startp
, len
) < 0)
114 * Determine if the path is a Windows prefix and, if so, returns
115 * its actual lentgh. If it is not a prefix, returns -1.
117 static int win32_prefix_length(const char *path
, int len
)
124 * Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return
127 if (len
== 2 && LOOKS_LIKE_DRIVE_PREFIX(path
))
131 * Similarly checks if we're dealing with a network computer name
132 * '//computername/.git' will return '//computername/'
134 if (looks_like_network_computer_name(path
, len
))
142 * Based on the Android implementation, BSD licensed.
143 * Check http://android.git.kernel.org/
145 int git_path_dirname_r(git_buf
*buffer
, const char *path
)
148 int is_prefix
= 0, len
;
150 /* Empty or NULL string gets treated as "." */
151 if (path
== NULL
|| *path
== '\0') {
157 /* Strip trailing slashes */
158 endp
= path
+ strlen(path
) - 1;
159 while (endp
> path
&& *endp
== '/')
162 if ((len
= win32_prefix_length(path
, endp
- path
+ 1)) > 0) {
167 /* Find the start of the dir */
168 while (endp
> path
&& *endp
!= '/')
171 /* Either the dir is "/" or there are no slashes */
173 path
= (*endp
== '/') ? "/" : ".";
180 } while (endp
> path
&& *endp
== '/');
182 if ((len
= win32_prefix_length(path
, endp
- path
+ 1)) > 0) {
187 /* Cast is safe because max path < max int */
188 len
= (int)(endp
- path
+ 1);
192 if (git_buf_set(buffer
, path
, len
) < 0)
194 if (is_prefix
&& git_buf_putc(buffer
, '/') < 0)
202 char *git_path_dirname(const char *path
)
204 git_buf buf
= GIT_BUF_INIT
;
207 git_path_dirname_r(&buf
, path
);
208 dirname
= git_buf_detach(&buf
);
209 git_buf_free(&buf
); /* avoid memleak if error occurs */
214 char *git_path_basename(const char *path
)
216 git_buf buf
= GIT_BUF_INIT
;
219 git_path_basename_r(&buf
, path
);
220 basename
= git_buf_detach(&buf
);
221 git_buf_free(&buf
); /* avoid memleak if error occurs */
226 size_t git_path_basename_offset(git_buf
*buffer
)
230 if (!buffer
|| buffer
->size
<= 0)
233 slash
= git_buf_rfind_next(buffer
, '/');
235 if (slash
>= 0 && buffer
->ptr
[slash
] == '/')
236 return (size_t)(slash
+ 1);
241 const char *git_path_topdir(const char *path
)
249 if (!len
|| path
[len
- 1] != '/')
252 for (i
= (ssize_t
)len
- 2; i
>= 0; --i
)
259 int git_path_root(const char *path
)
263 /* Does the root of the path look like a windows drive ? */
264 if (LOOKS_LIKE_DRIVE_PREFIX(path
))
268 /* Are we dealing with a windows network path? */
269 else if ((path
[0] == '/' && path
[1] == '/' && path
[2] != '/') ||
270 (path
[0] == '\\' && path
[1] == '\\' && path
[2] != '\\'))
274 /* Skip the computer name segment */
275 while (path
[offset
] && path
[offset
] != '/' && path
[offset
] != '\\')
280 if (path
[offset
] == '/' || path
[offset
] == '\\')
283 return -1; /* Not a real error - signals that path is not rooted */
286 void git_path_trim_slashes(git_buf
*path
)
288 int ceiling
= git_path_root(path
->ptr
) + 1;
289 assert(ceiling
>= 0);
291 while (path
->size
> (size_t)ceiling
) {
292 if (path
->ptr
[path
->size
-1] != '/')
295 path
->ptr
[path
->size
-1] = '\0';
300 int git_path_join_unrooted(
301 git_buf
*path_out
, const char *path
, const char *base
, ssize_t
*root_at
)
305 assert(path
&& path_out
);
307 root
= (ssize_t
)git_path_root(path
);
309 if (base
!= NULL
&& root
< 0) {
310 if (git_buf_joinpath(path_out
, base
, path
) < 0)
313 root
= (ssize_t
)strlen(base
);
315 if (git_buf_sets(path_out
, path
) < 0)
321 git_path_equal_or_prefixed(base
, path
, &root
);
330 void git_path_squash_slashes(git_buf
*path
)
337 for (p
= path
->ptr
, q
= path
->ptr
; *q
; p
++, q
++) {
340 while (*q
== '/' && *(q
+1) == '/') {
349 int git_path_prettify(git_buf
*path_out
, const char *path
, const char *base
)
351 char buf
[GIT_PATH_MAX
];
353 assert(path
&& path_out
);
355 /* construct path if needed */
356 if (base
!= NULL
&& git_path_root(path
) < 0) {
357 if (git_buf_joinpath(path_out
, base
, path
) < 0)
359 path
= path_out
->ptr
;
362 if (p_realpath(path
, buf
) == NULL
) {
363 /* giterr_set resets the errno when dealing with a GITERR_OS kind of error */
364 int error
= (errno
== ENOENT
|| errno
== ENOTDIR
) ? GIT_ENOTFOUND
: -1;
365 giterr_set(GITERR_OS
, "failed to resolve path '%s'", path
);
367 git_buf_clear(path_out
);
372 return git_buf_sets(path_out
, buf
);
375 int git_path_prettify_dir(git_buf
*path_out
, const char *path
, const char *base
)
377 int error
= git_path_prettify(path_out
, path
, base
);
378 return (error
< 0) ? error
: git_path_to_dir(path_out
);
381 int git_path_to_dir(git_buf
*path
)
383 if (path
->asize
> 0 &&
384 git_buf_len(path
) > 0 &&
385 path
->ptr
[git_buf_len(path
) - 1] != '/')
386 git_buf_putc(path
, '/');
388 return git_buf_oom(path
) ? -1 : 0;
391 void git_path_string_to_dir(char* path
, size_t size
)
393 size_t end
= strlen(path
);
395 if (end
&& path
[end
- 1] != '/' && end
< size
) {
397 path
[end
+ 1] = '\0';
401 int git__percent_decode(git_buf
*decoded_out
, const char *input
)
404 assert(decoded_out
&& input
);
406 len
= (int)strlen(input
);
407 git_buf_clear(decoded_out
);
409 for(i
= 0; i
< len
; i
++)
419 hi
= git__fromhex(input
[i
+ 1]);
420 lo
= git__fromhex(input
[i
+ 2]);
422 if (hi
< 0 || lo
< 0)
425 c
= (char)(hi
<< 4 | lo
);
429 if (git_buf_putc(decoded_out
, c
) < 0)
436 static int error_invalid_local_file_uri(const char *uri
)
438 giterr_set(GITERR_CONFIG
, "'%s' is not a valid local file URI", uri
);
442 static int local_file_url_prefixlen(const char *file_url
)
446 if (git__prefixcmp(file_url
, "file://") == 0) {
447 if (file_url
[7] == '/')
449 else if (git__prefixcmp(file_url
+ 7, "localhost/") == 0)
456 bool git_path_is_local_file_url(const char *file_url
)
458 return (local_file_url_prefixlen(file_url
) > 0);
461 int git_path_fromurl(git_buf
*local_path_out
, const char *file_url
)
465 assert(local_path_out
&& file_url
);
467 if ((offset
= local_file_url_prefixlen(file_url
)) < 0 ||
468 file_url
[offset
] == '\0' || file_url
[offset
] == '/')
469 return error_invalid_local_file_uri(file_url
);
472 offset
--; /* A *nix absolute path starts with a forward slash */
475 git_buf_clear(local_path_out
);
476 return git__percent_decode(local_path_out
, file_url
+ offset
);
479 int git_path_walk_up(
482 int (*cb
)(void *data
, const char *),
487 ssize_t stop
= 0, scan
;
492 if (ceiling
!= NULL
) {
493 if (git__prefixcmp(path
->ptr
, ceiling
) == 0)
494 stop
= (ssize_t
)strlen(ceiling
);
496 stop
= git_buf_len(path
);
498 scan
= git_buf_len(path
);
500 /* empty path: yield only once */
502 error
= cb(data
, "");
504 giterr_set_after_callback(error
);
508 iter
.ptr
= path
->ptr
;
509 iter
.size
= git_buf_len(path
);
510 iter
.asize
= path
->asize
;
512 while (scan
>= stop
) {
513 error
= cb(data
, iter
.ptr
);
514 iter
.ptr
[scan
] = oldc
;
517 giterr_set_after_callback(error
);
521 scan
= git_buf_rfind_next(&iter
, '/');
524 oldc
= iter
.ptr
[scan
];
526 iter
.ptr
[scan
] = '\0';
531 iter
.ptr
[scan
] = oldc
;
533 /* relative path: yield for the last component */
534 if (!error
&& stop
== 0 && iter
.ptr
[0] != '/') {
535 error
= cb(data
, "");
537 giterr_set_after_callback(error
);
543 bool git_path_exists(const char *path
)
546 return p_access(path
, F_OK
) == 0;
549 bool git_path_isdir(const char *path
)
552 if (p_stat(path
, &st
) < 0)
555 return S_ISDIR(st
.st_mode
) != 0;
558 bool git_path_isfile(const char *path
)
563 if (p_stat(path
, &st
) < 0)
566 return S_ISREG(st
.st_mode
) != 0;
569 bool git_path_islink(const char *path
)
574 if (p_lstat(path
, &st
) < 0)
577 return S_ISLNK(st
.st_mode
) != 0;
582 bool git_path_is_empty_dir(const char *path
)
584 git_win32_path filter_w
;
587 if (git_win32__findfirstfile_filter(filter_w
, path
)) {
588 WIN32_FIND_DATAW findData
;
589 HANDLE hFind
= FindFirstFileW(filter_w
, &findData
);
591 /* FindFirstFile will fail if there are no children to the given
592 * path, which can happen if the given path is a file (and obviously
593 * has no children) or if the given path is an empty mount point.
594 * (Most directories have at least directory entries '.' and '..',
595 * but ridiculously another volume mounted in another drive letter's
596 * path space do not, and thus have nothing to enumerate.) If
597 * FindFirstFile fails, check if this is a directory-like thing
600 if (hFind
== INVALID_HANDLE_VALUE
)
601 return git_path_isdir(path
);
603 /* If the find handle was created successfully, then it's a directory */
607 /* Allow the enumeration to return . and .. and still be considered
608 * empty. In the special case of drive roots (i.e. C:\) where . and
609 * .. do not occur, we can still consider the path to be an empty
610 * directory if there's nothing there. */
611 if (!git_path_is_dot_or_dotdotW(findData
.cFileName
)) {
615 } while (FindNextFileW(hFind
, &findData
));
625 static int path_found_entry(void *payload
, git_buf
*path
)
628 return !git_path_is_dot_or_dotdot(path
->ptr
);
631 bool git_path_is_empty_dir(const char *path
)
634 git_buf dir
= GIT_BUF_INIT
;
636 if (!git_path_isdir(path
))
639 if ((error
= git_buf_sets(&dir
, path
)) != 0)
642 error
= git_path_direach(&dir
, 0, path_found_entry
, NULL
);
651 int git_path_set_error(int errno_value
, const char *path
, const char *action
)
653 switch (errno_value
) {
656 giterr_set(GITERR_OS
, "could not find '%s' to %s", path
, action
);
657 return GIT_ENOTFOUND
;
661 giterr_set(GITERR_OS
, "invalid path for filesystem '%s'", path
);
662 return GIT_EINVALIDSPEC
;
665 giterr_set(GITERR_OS
, "failed %s - '%s' already exists", action
, path
);
669 giterr_set(GITERR_OS
, "failed %s - '%s' is locked", action
, path
);
673 giterr_set(GITERR_OS
, "could not %s '%s'", action
, path
);
678 int git_path_lstat(const char *path
, struct stat
*st
)
680 if (p_lstat(path
, st
) == 0)
683 return git_path_set_error(errno
, path
, "stat");
686 static bool _check_dir_contents(
689 bool (*predicate
)(const char *))
692 size_t dir_size
= git_buf_len(dir
);
693 size_t sub_size
= strlen(sub
);
696 /* leave base valid even if we could not make space for subdir */
697 if (GIT_ADD_SIZET_OVERFLOW(&alloc_size
, dir_size
, sub_size
) ||
698 GIT_ADD_SIZET_OVERFLOW(&alloc_size
, alloc_size
, 2) ||
699 git_buf_try_grow(dir
, alloc_size
, false) < 0)
703 if (git_buf_joinpath(dir
, dir
->ptr
, sub
) < 0)
706 result
= predicate(dir
->ptr
);
709 git_buf_truncate(dir
, dir_size
);
713 bool git_path_contains(git_buf
*dir
, const char *item
)
715 return _check_dir_contents(dir
, item
, &git_path_exists
);
718 bool git_path_contains_dir(git_buf
*base
, const char *subdir
)
720 return _check_dir_contents(base
, subdir
, &git_path_isdir
);
723 bool git_path_contains_file(git_buf
*base
, const char *file
)
725 return _check_dir_contents(base
, file
, &git_path_isfile
);
728 int git_path_find_dir(git_buf
*dir
, const char *path
, const char *base
)
730 int error
= git_path_join_unrooted(dir
, path
, base
, NULL
);
733 char buf
[GIT_PATH_MAX
];
734 if (p_realpath(dir
->ptr
, buf
) != NULL
)
735 error
= git_buf_sets(dir
, buf
);
738 /* call dirname if this is not a directory */
739 if (!error
) /* && git_path_isdir(dir->ptr) == false) */
740 error
= (git_path_dirname_r(dir
, dir
->ptr
) < 0) ? -1 : 0;
743 error
= git_path_to_dir(dir
);
748 int git_path_resolve_relative(git_buf
*path
, size_t ceiling
)
750 char *base
, *to
, *from
, *next
;
753 GITERR_CHECK_ALLOC_BUF(path
);
755 if (ceiling
> path
->size
)
756 ceiling
= path
->size
;
758 /* recognize drive prefixes, etc. that should not be backed over */
760 ceiling
= git_path_root(path
->ptr
) + 1;
762 /* recognize URL prefixes that should not be backed over */
764 for (next
= path
->ptr
; *next
&& git__isalpha(*next
); ++next
);
765 if (next
[0] == ':' && next
[1] == '/' && next
[2] == '/')
766 ceiling
= (next
+ 3) - path
->ptr
;
769 base
= to
= from
= path
->ptr
+ ceiling
;
772 for (next
= from
; *next
&& *next
!= '/'; ++next
);
776 if (len
== 1 && from
[0] == '.')
777 /* do nothing with singleton dot */;
779 else if (len
== 2 && from
[0] == '.' && from
[1] == '.') {
780 /* error out if trying to up one from a hard base */
781 if (to
== base
&& ceiling
!= 0) {
782 giterr_set(GITERR_INVALID
,
783 "cannot strip root component off url");
787 /* no more path segments to strip,
788 * use '../' as a new base path */
794 memmove(to
, from
, len
);
797 /* this is now the base, can't back up from a
801 /* back up a path segment */
802 while (to
> base
&& to
[-1] == '/') to
--;
803 while (to
> base
&& to
[-1] != '/') to
--;
806 if (*next
== '/' && *from
!= '/')
810 memmove(to
, from
, len
);
817 while (*from
== '/') from
++;
822 path
->size
= to
- path
->ptr
;
827 int git_path_apply_relative(git_buf
*target
, const char *relpath
)
829 return git_buf_joinpath(target
, git_buf_cstr(target
), relpath
) ||
830 git_path_resolve_relative(target
, 0);
834 const char *name1
, size_t len1
, int isdir1
,
835 const char *name2
, size_t len2
, int isdir2
,
836 int (*compare
)(const char *, const char *, size_t))
838 unsigned char c1
, c2
;
839 size_t len
= len1
< len2
? len1
: len2
;
842 cmp
= compare(name1
, name2
, len
);
849 if (c1
== '\0' && isdir1
)
852 if (c2
== '\0' && isdir2
)
855 return (c1
< c2
) ? -1 : (c1
> c2
) ? 1 : 0;
858 size_t git_path_common_dirlen(const char *one
, const char *two
)
860 const char *p
, *q
, *dirsep
= NULL
;
862 for (p
= one
, q
= two
; *p
&& *q
; p
++, q
++) {
863 if (*p
== '/' && *q
== '/')
869 return dirsep
? (dirsep
- one
) + 1 : 0;
872 int git_path_make_relative(git_buf
*path
, const char *parent
)
874 const char *p
, *q
, *p_dirsep
, *q_dirsep
;
875 size_t plen
= path
->size
, newlen
, alloclen
, depth
= 1, i
, offset
;
877 for (p_dirsep
= p
= path
->ptr
, q_dirsep
= q
= parent
; *p
&& *q
; p
++, q
++) {
878 if (*p
== '/' && *q
== '/') {
886 /* need at least 1 common path segment */
887 if ((p_dirsep
== path
->ptr
|| q_dirsep
== parent
) &&
888 (*p_dirsep
!= '/' || *q_dirsep
!= '/')) {
889 giterr_set(GITERR_INVALID
,
890 "%s is not a parent of %s", parent
, path
->ptr
);
891 return GIT_ENOTFOUND
;
894 if (*p
== '/' && !*q
)
896 else if (!*p
&& *q
== '/')
899 return git_buf_clear(path
), 0;
905 plen
-= (p
- path
->ptr
);
908 return git_buf_set(path
, p
, plen
);
910 for (; (q
= strchr(q
, '/')) && *(q
+ 1); q
++)
913 GITERR_CHECK_ALLOC_MULTIPLY(&newlen
, depth
, 3);
914 GITERR_CHECK_ALLOC_ADD(&newlen
, newlen
, plen
);
916 GITERR_CHECK_ALLOC_ADD(&alloclen
, newlen
, 1);
918 /* save the offset as we might realllocate the pointer */
919 offset
= p
- path
->ptr
;
920 if (git_buf_try_grow(path
, alloclen
, 1) < 0)
922 p
= path
->ptr
+ offset
;
924 memmove(path
->ptr
+ (depth
* 3), p
, plen
+ 1);
926 for (i
= 0; i
< depth
; i
++)
927 memcpy(path
->ptr
+ (i
* 3), "../", 3);
933 bool git_path_has_non_ascii(const char *path
, size_t pathlen
)
935 const uint8_t *scan
= (const uint8_t *)path
, *end
;
937 for (end
= scan
+ pathlen
; scan
< end
; ++scan
)
946 int git_path_iconv_init_precompose(git_path_iconv_t
*ic
)
948 git_buf_init(&ic
->buf
, 0);
949 ic
->map
= iconv_open(GIT_PATH_REPO_ENCODING
, GIT_PATH_NATIVE_ENCODING
);
953 void git_path_iconv_clear(git_path_iconv_t
*ic
)
956 if (ic
->map
!= (iconv_t
)-1)
957 iconv_close(ic
->map
);
958 git_buf_free(&ic
->buf
);
962 int git_path_iconv(git_path_iconv_t
*ic
, const char **in
, size_t *inlen
)
964 char *nfd
= (char*)*in
, *nfc
;
965 size_t nfdlen
= *inlen
, nfclen
, wantlen
= nfdlen
, alloclen
, rv
;
968 if (!ic
|| ic
->map
== (iconv_t
)-1 ||
969 !git_path_has_non_ascii(*in
, *inlen
))
972 git_buf_clear(&ic
->buf
);
975 GITERR_CHECK_ALLOC_ADD(&alloclen
, wantlen
, 1);
976 if (git_buf_grow(&ic
->buf
, alloclen
) < 0)
979 nfc
= ic
->buf
.ptr
+ ic
->buf
.size
;
980 nfclen
= ic
->buf
.asize
- ic
->buf
.size
;
982 rv
= iconv(ic
->map
, &nfd
, &nfdlen
, &nfc
, &nfclen
);
984 ic
->buf
.size
= (nfc
- ic
->buf
.ptr
);
986 if (rv
!= (size_t)-1)
989 /* if we cannot convert the data (probably because iconv thinks
990 * it is not valid UTF-8 source data), then use original data
995 /* make space for 2x the remaining data to be converted
996 * (with per retry overhead to avoid infinite loops)
998 wantlen
= ic
->buf
.size
+ max(nfclen
, nfdlen
) * 2 + (size_t)(retry
* 4);
1004 ic
->buf
.ptr
[ic
->buf
.size
] = '\0';
1007 *inlen
= ic
->buf
.size
;
1012 giterr_set(GITERR_OS
, "unable to convert unicode path data");
1016 static const char *nfc_file
= "\xC3\x85\x73\x74\x72\xC3\xB6\x6D.XXXXXX";
1017 static const char *nfd_file
= "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D.XXXXXX";
1019 /* Check if the platform is decomposing unicode data for us. We will
1020 * emulate core Git and prefer to use precomposed unicode data internally
1021 * on these platforms, composing the decomposed unicode on the fly.
1023 * This mainly happens on the Mac where HDFS stores filenames as
1024 * decomposed unicode. Even on VFAT and SAMBA file systems, the Mac will
1025 * return decomposed unicode from readdir() even when the actual
1026 * filesystem is storing precomposed unicode.
1028 bool git_path_does_fs_decompose_unicode(const char *root
)
1030 git_buf path
= GIT_BUF_INIT
;
1032 bool found_decomposed
= false;
1035 /* Create a file using a precomposed path and then try to find it
1036 * using the decomposed name. If the lookup fails, then we will mark
1037 * that we should precompose unicode for this repository.
1039 if (git_buf_joinpath(&path
, root
, nfc_file
) < 0 ||
1040 (fd
= p_mkstemp(path
.ptr
)) < 0)
1044 /* record trailing digits generated by mkstemp */
1045 memcpy(tmp
, path
.ptr
+ path
.size
- sizeof(tmp
), sizeof(tmp
));
1047 /* try to look up as NFD path */
1048 if (git_buf_joinpath(&path
, root
, nfd_file
) < 0)
1050 memcpy(path
.ptr
+ path
.size
- sizeof(tmp
), tmp
, sizeof(tmp
));
1052 found_decomposed
= git_path_exists(path
.ptr
);
1054 /* remove temporary file (using original precomposed path) */
1055 if (git_buf_joinpath(&path
, root
, nfc_file
) < 0)
1057 memcpy(path
.ptr
+ path
.size
- sizeof(tmp
), tmp
, sizeof(tmp
));
1059 (void)p_unlink(path
.ptr
);
1062 git_buf_free(&path
);
1063 return found_decomposed
;
1068 bool git_path_does_fs_decompose_unicode(const char *root
)
1076 #if defined(__sun) || defined(__GNU__)
1077 typedef char path_dirent_data
[sizeof(struct dirent
) + FILENAME_MAX
+ 1];
1079 typedef struct dirent path_dirent_data
;
1082 int git_path_direach(
1085 int (*fn
)(void *, git_buf
*),
1093 #ifdef GIT_USE_ICONV
1094 git_path_iconv_t ic
= GIT_PATH_ICONV_INIT
;
1099 if (git_path_to_dir(path
) < 0)
1102 wd_len
= git_buf_len(path
);
1104 if ((dir
= opendir(path
->ptr
)) == NULL
) {
1105 giterr_set(GITERR_OS
, "failed to open directory '%s'", path
->ptr
);
1106 if (errno
== ENOENT
)
1107 return GIT_ENOTFOUND
;
1112 #ifdef GIT_USE_ICONV
1113 if ((flags
& GIT_PATH_DIR_PRECOMPOSE_UNICODE
) != 0)
1114 (void)git_path_iconv_init_precompose(&ic
);
1117 while ((de
= readdir(dir
)) != NULL
) {
1118 const char *de_path
= de
->d_name
;
1119 size_t de_len
= strlen(de_path
);
1121 if (git_path_is_dot_or_dotdot(de_path
))
1124 #ifdef GIT_USE_ICONV
1125 if ((error
= git_path_iconv(&ic
, &de_path
, &de_len
)) < 0)
1129 if ((error
= git_buf_put(path
, de_path
, de_len
)) < 0)
1133 error
= fn(arg
, path
);
1135 git_buf_truncate(path
, wd_len
); /* restore path */
1137 /* Only set our own error if the callback did not set one already */
1140 giterr_set_after_callback(error
);
1148 #ifdef GIT_USE_ICONV
1149 git_path_iconv_clear(&ic
);
1155 #if defined(GIT_WIN32) && !defined(__MINGW32__)
1157 /* Using _FIND_FIRST_EX_LARGE_FETCH may increase performance in Windows 7
1160 #ifndef FIND_FIRST_EX_LARGE_FETCH
1161 # define FIND_FIRST_EX_LARGE_FETCH 2
1164 int git_path_diriter_init(
1165 git_path_diriter
*diriter
,
1169 git_win32_path path_filter
;
1171 static int is_win7_or_later
= -1;
1172 if (is_win7_or_later
< 0)
1173 is_win7_or_later
= git_has_win32_version(6, 1, 0);
1175 assert(diriter
&& path
);
1177 memset(diriter
, 0, sizeof(git_path_diriter
));
1178 diriter
->handle
= INVALID_HANDLE_VALUE
;
1180 if (git_buf_puts(&diriter
->path_utf8
, path
) < 0)
1183 git_path_trim_slashes(&diriter
->path_utf8
);
1185 if (diriter
->path_utf8
.size
== 0) {
1186 giterr_set(GITERR_FILESYSTEM
, "could not open directory '%s'", path
);
1190 if ((diriter
->parent_len
= git_win32_path_from_utf8(diriter
->path
, diriter
->path_utf8
.ptr
)) < 0 ||
1191 !git_win32__findfirstfile_filter(path_filter
, diriter
->path_utf8
.ptr
)) {
1192 giterr_set(GITERR_OS
, "could not parse the directory path '%s'", path
);
1196 diriter
->handle
= FindFirstFileExW(
1198 is_win7_or_later
? FindExInfoBasic
: FindExInfoStandard
,
1200 FindExSearchNameMatch
,
1202 is_win7_or_later
? FIND_FIRST_EX_LARGE_FETCH
: 0);
1204 if (diriter
->handle
== INVALID_HANDLE_VALUE
) {
1205 giterr_set(GITERR_OS
, "could not open directory '%s'", path
);
1209 diriter
->parent_utf8_len
= diriter
->path_utf8
.size
;
1210 diriter
->flags
= flags
;
1214 static int diriter_update_paths(git_path_diriter
*diriter
)
1216 size_t filename_len
, path_len
;
1218 filename_len
= wcslen(diriter
->current
.cFileName
);
1220 if (GIT_ADD_SIZET_OVERFLOW(&path_len
, diriter
->parent_len
, filename_len
) ||
1221 GIT_ADD_SIZET_OVERFLOW(&path_len
, path_len
, 2))
1224 if (path_len
> GIT_WIN_PATH_UTF16
) {
1225 giterr_set(GITERR_FILESYSTEM
,
1226 "invalid path '%.*ls\\%ls' (path too long)",
1227 diriter
->parent_len
, diriter
->path
, diriter
->current
.cFileName
);
1231 diriter
->path
[diriter
->parent_len
] = L
'\\';
1232 memcpy(&diriter
->path
[diriter
->parent_len
+1],
1233 diriter
->current
.cFileName
, filename_len
* sizeof(wchar_t));
1234 diriter
->path
[path_len
-1] = L
'\0';
1236 git_buf_truncate(&diriter
->path_utf8
, diriter
->parent_utf8_len
);
1238 if (diriter
->parent_utf8_len
> 0 &&
1239 diriter
->path_utf8
.ptr
[diriter
->parent_utf8_len
-1] != '/')
1240 git_buf_putc(&diriter
->path_utf8
, '/');
1242 git_buf_put_w(&diriter
->path_utf8
, diriter
->current
.cFileName
, filename_len
);
1244 if (git_buf_oom(&diriter
->path_utf8
))
1250 int git_path_diriter_next(git_path_diriter
*diriter
)
1252 bool skip_dot
= !(diriter
->flags
& GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT
);
1255 /* Our first time through, we already have the data from
1256 * FindFirstFileW. Use it, otherwise get the next file.
1258 if (!diriter
->needs_next
)
1259 diriter
->needs_next
= 1;
1260 else if (!FindNextFileW(diriter
->handle
, &diriter
->current
))
1261 return GIT_ITEROVER
;
1262 } while (skip_dot
&& git_path_is_dot_or_dotdotW(diriter
->current
.cFileName
));
1264 if (diriter_update_paths(diriter
) < 0)
1270 int git_path_diriter_filename(
1273 git_path_diriter
*diriter
)
1275 assert(out
&& out_len
&& diriter
);
1277 assert(diriter
->path_utf8
.size
> diriter
->parent_utf8_len
);
1279 *out
= &diriter
->path_utf8
.ptr
[diriter
->parent_utf8_len
+1];
1280 *out_len
= diriter
->path_utf8
.size
- diriter
->parent_utf8_len
- 1;
1284 int git_path_diriter_fullpath(
1287 git_path_diriter
*diriter
)
1289 assert(out
&& out_len
&& diriter
);
1291 *out
= diriter
->path_utf8
.ptr
;
1292 *out_len
= diriter
->path_utf8
.size
;
1296 int git_path_diriter_stat(struct stat
*out
, git_path_diriter
*diriter
)
1298 assert(out
&& diriter
);
1300 return git_win32__file_attribute_to_stat(out
,
1301 (WIN32_FILE_ATTRIBUTE_DATA
*)&diriter
->current
,
1305 void git_path_diriter_free(git_path_diriter
*diriter
)
1307 if (diriter
== NULL
)
1310 git_buf_free(&diriter
->path_utf8
);
1312 if (diriter
->handle
!= INVALID_HANDLE_VALUE
) {
1313 FindClose(diriter
->handle
);
1314 diriter
->handle
= INVALID_HANDLE_VALUE
;
1320 int git_path_diriter_init(
1321 git_path_diriter
*diriter
,
1325 assert(diriter
&& path
);
1327 memset(diriter
, 0, sizeof(git_path_diriter
));
1329 if (git_buf_puts(&diriter
->path
, path
) < 0)
1332 git_path_trim_slashes(&diriter
->path
);
1334 if (diriter
->path
.size
== 0) {
1335 giterr_set(GITERR_FILESYSTEM
, "could not open directory '%s'", path
);
1339 if ((diriter
->dir
= opendir(diriter
->path
.ptr
)) == NULL
) {
1340 git_buf_free(&diriter
->path
);
1342 giterr_set(GITERR_OS
, "failed to open directory '%s'", path
);
1346 #ifdef GIT_USE_ICONV
1347 if ((flags
& GIT_PATH_DIR_PRECOMPOSE_UNICODE
) != 0)
1348 (void)git_path_iconv_init_precompose(&diriter
->ic
);
1351 diriter
->parent_len
= diriter
->path
.size
;
1352 diriter
->flags
= flags
;
1357 int git_path_diriter_next(git_path_diriter
*diriter
)
1360 const char *filename
;
1361 size_t filename_len
;
1362 bool skip_dot
= !(diriter
->flags
& GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT
);
1370 if ((de
= readdir(diriter
->dir
)) == NULL
) {
1372 return GIT_ITEROVER
;
1374 giterr_set(GITERR_OS
,
1375 "could not read directory '%s'", diriter
->path
.ptr
);
1378 } while (skip_dot
&& git_path_is_dot_or_dotdot(de
->d_name
));
1380 filename
= de
->d_name
;
1381 filename_len
= strlen(filename
);
1383 #ifdef GIT_USE_ICONV
1384 if ((diriter
->flags
& GIT_PATH_DIR_PRECOMPOSE_UNICODE
) != 0 &&
1385 (error
= git_path_iconv(&diriter
->ic
, &filename
, &filename_len
)) < 0)
1389 git_buf_truncate(&diriter
->path
, diriter
->parent_len
);
1391 if (diriter
->parent_len
> 0 &&
1392 diriter
->path
.ptr
[diriter
->parent_len
-1] != '/')
1393 git_buf_putc(&diriter
->path
, '/');
1395 git_buf_put(&diriter
->path
, filename
, filename_len
);
1397 if (git_buf_oom(&diriter
->path
))
1403 int git_path_diriter_filename(
1406 git_path_diriter
*diriter
)
1408 assert(out
&& out_len
&& diriter
);
1410 assert(diriter
->path
.size
> diriter
->parent_len
);
1412 *out
= &diriter
->path
.ptr
[diriter
->parent_len
+1];
1413 *out_len
= diriter
->path
.size
- diriter
->parent_len
- 1;
1417 int git_path_diriter_fullpath(
1420 git_path_diriter
*diriter
)
1422 assert(out
&& out_len
&& diriter
);
1424 *out
= diriter
->path
.ptr
;
1425 *out_len
= diriter
->path
.size
;
1429 int git_path_diriter_stat(struct stat
*out
, git_path_diriter
*diriter
)
1431 assert(out
&& diriter
);
1433 return git_path_lstat(diriter
->path
.ptr
, out
);
1436 void git_path_diriter_free(git_path_diriter
*diriter
)
1438 if (diriter
== NULL
)
1442 closedir(diriter
->dir
);
1443 diriter
->dir
= NULL
;
1446 #ifdef GIT_USE_ICONV
1447 git_path_iconv_clear(&diriter
->ic
);
1450 git_buf_free(&diriter
->path
);
1455 int git_path_dirload(
1456 git_vector
*contents
,
1461 git_path_diriter iter
= GIT_PATH_DIRITER_INIT
;
1467 assert(contents
&& path
);
1469 if ((error
= git_path_diriter_init(&iter
, path
, flags
)) < 0)
1472 while ((error
= git_path_diriter_next(&iter
)) == 0) {
1473 if ((error
= git_path_diriter_fullpath(&name
, &name_len
, &iter
)) < 0)
1476 assert(name_len
> prefix_len
);
1478 dup
= git__strndup(name
+ prefix_len
, name_len
- prefix_len
);
1479 GITERR_CHECK_ALLOC(dup
);
1481 if ((error
= git_vector_insert(contents
, dup
)) < 0)
1485 if (error
== GIT_ITEROVER
)
1488 git_path_diriter_free(&iter
);
1492 int git_path_from_url_or_path(git_buf
*local_path_out
, const char *url_or_path
)
1494 if (git_path_is_local_file_url(url_or_path
))
1495 return git_path_fromurl(local_path_out
, url_or_path
);
1497 return git_buf_sets(local_path_out
, url_or_path
);
1500 /* Reject paths like AUX or COM1, or those versions that end in a dot or
1501 * colon. ("AUX." or "AUX:")
1503 GIT_INLINE(bool) verify_dospath(
1504 const char *component
,
1506 const char dospath
[3],
1509 size_t last
= trailing_num
? 4 : 3;
1511 if (len
< last
|| git__strncasecmp(component
, dospath
, 3) != 0)
1514 if (trailing_num
&& (component
[3] < '1' || component
[3] > '9'))
1517 return (len
> last
&&
1518 component
[last
] != '.' &&
1519 component
[last
] != ':');
1522 static int32_t next_hfs_char(const char **in
, size_t *len
)
1526 int cp_len
= git__utf8_iterate((const uint8_t *)(*in
), (int)(*len
), &codepoint
);
1533 /* these code points are ignored completely */
1534 switch (codepoint
) {
1535 case 0x200c: /* ZERO WIDTH NON-JOINER */
1536 case 0x200d: /* ZERO WIDTH JOINER */
1537 case 0x200e: /* LEFT-TO-RIGHT MARK */
1538 case 0x200f: /* RIGHT-TO-LEFT MARK */
1539 case 0x202a: /* LEFT-TO-RIGHT EMBEDDING */
1540 case 0x202b: /* RIGHT-TO-LEFT EMBEDDING */
1541 case 0x202c: /* POP DIRECTIONAL FORMATTING */
1542 case 0x202d: /* LEFT-TO-RIGHT OVERRIDE */
1543 case 0x202e: /* RIGHT-TO-LEFT OVERRIDE */
1544 case 0x206a: /* INHIBIT SYMMETRIC SWAPPING */
1545 case 0x206b: /* ACTIVATE SYMMETRIC SWAPPING */
1546 case 0x206c: /* INHIBIT ARABIC FORM SHAPING */
1547 case 0x206d: /* ACTIVATE ARABIC FORM SHAPING */
1548 case 0x206e: /* NATIONAL DIGIT SHAPES */
1549 case 0x206f: /* NOMINAL DIGIT SHAPES */
1550 case 0xfeff: /* ZERO WIDTH NO-BREAK SPACE */
1554 /* fold into lowercase -- this will only fold characters in
1555 * the ASCII range, which is perfectly fine, because the
1556 * git folder name can only be composed of ascii characters
1558 return git__tolower(codepoint
);
1560 return 0; /* NULL byte -- end of string */
1563 static bool verify_dotgit_hfs(const char *path
, size_t len
)
1565 if (next_hfs_char(&path
, &len
) != '.' ||
1566 next_hfs_char(&path
, &len
) != 'g' ||
1567 next_hfs_char(&path
, &len
) != 'i' ||
1568 next_hfs_char(&path
, &len
) != 't' ||
1569 next_hfs_char(&path
, &len
) != 0)
1575 GIT_INLINE(bool) verify_dotgit_ntfs(git_repository
*repo
, const char *path
, size_t len
)
1577 git_buf
*reserved
= git_repository__reserved_names_win32
;
1578 size_t reserved_len
= git_repository__reserved_names_win32_len
;
1579 size_t start
= 0, i
;
1582 git_repository__reserved_names(&reserved
, &reserved_len
, repo
, true);
1584 for (i
= 0; i
< reserved_len
; i
++) {
1585 git_buf
*r
= &reserved
[i
];
1587 if (len
>= r
->size
&&
1588 strncasecmp(path
, r
->ptr
, r
->size
) == 0) {
1597 /* Reject paths like ".git\" */
1598 if (path
[start
] == '\\')
1601 /* Reject paths like '.git ' or '.git.' */
1602 for (i
= start
; i
< len
; i
++) {
1603 if (path
[i
] != ' ' && path
[i
] != '.')
1610 GIT_INLINE(bool) verify_char(unsigned char c
, unsigned int flags
)
1612 if ((flags
& GIT_PATH_REJECT_BACKSLASH
) && c
== '\\')
1615 if ((flags
& GIT_PATH_REJECT_SLASH
) && c
== '/')
1618 if (flags
& GIT_PATH_REJECT_NT_CHARS
) {
1638 * We fundamentally don't like some paths when dealing with user-inputted
1639 * strings (in checkout or ref names): we don't want dot or dot-dot
1640 * anywhere, we want to avoid writing weird paths on Windows that can't
1641 * be handled by tools that use the non-\\?\ APIs, we don't want slashes
1642 * or double slashes at the end of paths that can make them ambiguous.
1644 * For checkout, we don't want to recurse into ".git" either.
1646 static bool verify_component(
1647 git_repository
*repo
,
1648 const char *component
,
1655 if ((flags
& GIT_PATH_REJECT_TRAVERSAL
) &&
1656 len
== 1 && component
[0] == '.')
1659 if ((flags
& GIT_PATH_REJECT_TRAVERSAL
) &&
1660 len
== 2 && component
[0] == '.' && component
[1] == '.')
1663 if ((flags
& GIT_PATH_REJECT_TRAILING_DOT
) && component
[len
-1] == '.')
1666 if ((flags
& GIT_PATH_REJECT_TRAILING_SPACE
) && component
[len
-1] == ' ')
1669 if ((flags
& GIT_PATH_REJECT_TRAILING_COLON
) && component
[len
-1] == ':')
1672 if (flags
& GIT_PATH_REJECT_DOS_PATHS
) {
1673 if (!verify_dospath(component
, len
, "CON", false) ||
1674 !verify_dospath(component
, len
, "PRN", false) ||
1675 !verify_dospath(component
, len
, "AUX", false) ||
1676 !verify_dospath(component
, len
, "NUL", false) ||
1677 !verify_dospath(component
, len
, "COM", true) ||
1678 !verify_dospath(component
, len
, "LPT", true))
1682 if (flags
& GIT_PATH_REJECT_DOT_GIT_HFS
&&
1683 !verify_dotgit_hfs(component
, len
))
1686 if (flags
& GIT_PATH_REJECT_DOT_GIT_NTFS
&&
1687 !verify_dotgit_ntfs(repo
, component
, len
))
1690 /* don't bother rerunning the `.git` test if we ran the HFS or NTFS
1691 * specific tests, they would have already rejected `.git`.
1693 if ((flags
& GIT_PATH_REJECT_DOT_GIT_HFS
) == 0 &&
1694 (flags
& GIT_PATH_REJECT_DOT_GIT_NTFS
) == 0 &&
1695 (flags
& GIT_PATH_REJECT_DOT_GIT_LITERAL
) &&
1697 component
[0] == '.' &&
1698 (component
[1] == 'g' || component
[1] == 'G') &&
1699 (component
[2] == 'i' || component
[2] == 'I') &&
1700 (component
[3] == 't' || component
[3] == 'T'))
1706 GIT_INLINE(unsigned int) dotgit_flags(
1707 git_repository
*repo
,
1710 int protectHFS
= 0, protectNTFS
= 0;
1713 flags
|= GIT_PATH_REJECT_DOT_GIT_LITERAL
;
1723 if (repo
&& !protectHFS
)
1724 error
= git_repository__cvar(&protectHFS
, repo
, GIT_CVAR_PROTECTHFS
);
1725 if (!error
&& protectHFS
)
1726 flags
|= GIT_PATH_REJECT_DOT_GIT_HFS
;
1728 if (repo
&& !protectNTFS
)
1729 error
= git_repository__cvar(&protectNTFS
, repo
, GIT_CVAR_PROTECTNTFS
);
1730 if (!error
&& protectNTFS
)
1731 flags
|= GIT_PATH_REJECT_DOT_GIT_NTFS
;
1736 bool git_path_isvalid(
1737 git_repository
*repo
,
1741 const char *start
, *c
;
1743 /* Upgrade the ".git" checks based on platform */
1744 if ((flags
& GIT_PATH_REJECT_DOT_GIT
))
1745 flags
= dotgit_flags(repo
, flags
);
1747 for (start
= c
= path
; *c
; c
++) {
1748 if (!verify_char(*c
, flags
))
1752 if (!verify_component(repo
, start
, (c
- start
), flags
))
1759 return verify_component(repo
, start
, (c
- start
), flags
);
1762 int git_path_normalize_slashes(git_buf
*out
, const char *path
)
1767 if ((error
= git_buf_puts(out
, path
)) < 0)
1770 for (p
= out
->ptr
; *p
; p
++) {