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 * Based on the Android implementation, BSD licensed.
115 * Check http://android.git.kernel.org/
117 int git_path_dirname_r(git_buf
*buffer
, const char *path
)
122 /* Empty or NULL string gets treated as "." */
123 if (path
== NULL
|| *path
== '\0') {
129 /* Strip trailing slashes */
130 endp
= path
+ strlen(path
) - 1;
131 while (endp
> path
&& *endp
== '/')
134 /* Find the start of the dir */
135 while (endp
> path
&& *endp
!= '/')
138 /* Either the dir is "/" or there are no slashes */
140 path
= (*endp
== '/') ? "/" : ".";
147 } while (endp
> path
&& *endp
== '/');
149 /* Cast is safe because max path < max int */
150 len
= (int)(endp
- path
+ 1);
153 /* Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return
156 if (len
== 2 && LOOKS_LIKE_DRIVE_PREFIX(path
)) {
161 /* Similarly checks if we're dealing with a network computer name
162 '//computername/.git' will return '//computername/' */
164 if (looks_like_network_computer_name(path
, len
)) {
174 if (buffer
!= NULL
&& git_buf_set(buffer
, path
, len
) < 0)
181 char *git_path_dirname(const char *path
)
183 git_buf buf
= GIT_BUF_INIT
;
186 git_path_dirname_r(&buf
, path
);
187 dirname
= git_buf_detach(&buf
);
188 git_buf_free(&buf
); /* avoid memleak if error occurs */
193 char *git_path_basename(const char *path
)
195 git_buf buf
= GIT_BUF_INIT
;
198 git_path_basename_r(&buf
, path
);
199 basename
= git_buf_detach(&buf
);
200 git_buf_free(&buf
); /* avoid memleak if error occurs */
205 size_t git_path_basename_offset(git_buf
*buffer
)
209 if (!buffer
|| buffer
->size
<= 0)
212 slash
= git_buf_rfind_next(buffer
, '/');
214 if (slash
>= 0 && buffer
->ptr
[slash
] == '/')
215 return (size_t)(slash
+ 1);
220 const char *git_path_topdir(const char *path
)
228 if (!len
|| path
[len
- 1] != '/')
231 for (i
= (ssize_t
)len
- 2; i
>= 0; --i
)
238 int git_path_root(const char *path
)
242 /* Does the root of the path look like a windows drive ? */
243 if (LOOKS_LIKE_DRIVE_PREFIX(path
))
247 /* Are we dealing with a windows network path? */
248 else if ((path
[0] == '/' && path
[1] == '/' && path
[2] != '/') ||
249 (path
[0] == '\\' && path
[1] == '\\' && path
[2] != '\\'))
253 /* Skip the computer name segment */
254 while (path
[offset
] && path
[offset
] != '/' && path
[offset
] != '\\')
259 if (path
[offset
] == '/' || path
[offset
] == '\\')
262 return -1; /* Not a real error - signals that path is not rooted */
265 void git_path_trim_slashes(git_buf
*path
)
267 int ceiling
= git_path_root(path
->ptr
) + 1;
268 assert(ceiling
>= 0);
270 while (path
->size
> (size_t)ceiling
) {
271 if (path
->ptr
[path
->size
-1] != '/')
274 path
->ptr
[path
->size
-1] = '\0';
279 int git_path_join_unrooted(
280 git_buf
*path_out
, const char *path
, const char *base
, ssize_t
*root_at
)
284 assert(path
&& path_out
);
286 root
= (ssize_t
)git_path_root(path
);
288 if (base
!= NULL
&& root
< 0) {
289 if (git_buf_joinpath(path_out
, base
, path
) < 0)
292 root
= (ssize_t
)strlen(base
);
294 if (git_buf_sets(path_out
, path
) < 0)
300 git_path_equal_or_prefixed(base
, path
, &root
);
309 void git_path_squash_slashes(git_buf
*path
)
316 for (p
= path
->ptr
, q
= path
->ptr
; *q
; p
++, q
++) {
319 while (*q
== '/' && *(q
+1) == '/') {
328 int git_path_prettify(git_buf
*path_out
, const char *path
, const char *base
)
330 char buf
[GIT_PATH_MAX
];
332 assert(path
&& path_out
);
334 /* construct path if needed */
335 if (base
!= NULL
&& git_path_root(path
) < 0) {
336 if (git_buf_joinpath(path_out
, base
, path
) < 0)
338 path
= path_out
->ptr
;
341 if (p_realpath(path
, buf
) == NULL
) {
342 /* giterr_set resets the errno when dealing with a GITERR_OS kind of error */
343 int error
= (errno
== ENOENT
|| errno
== ENOTDIR
) ? GIT_ENOTFOUND
: -1;
344 giterr_set(GITERR_OS
, "Failed to resolve path '%s'", path
);
346 git_buf_clear(path_out
);
351 return git_buf_sets(path_out
, buf
);
354 int git_path_prettify_dir(git_buf
*path_out
, const char *path
, const char *base
)
356 int error
= git_path_prettify(path_out
, path
, base
);
357 return (error
< 0) ? error
: git_path_to_dir(path_out
);
360 int git_path_to_dir(git_buf
*path
)
362 if (path
->asize
> 0 &&
363 git_buf_len(path
) > 0 &&
364 path
->ptr
[git_buf_len(path
) - 1] != '/')
365 git_buf_putc(path
, '/');
367 return git_buf_oom(path
) ? -1 : 0;
370 void git_path_string_to_dir(char* path
, size_t size
)
372 size_t end
= strlen(path
);
374 if (end
&& path
[end
- 1] != '/' && end
< size
) {
376 path
[end
+ 1] = '\0';
380 int git__percent_decode(git_buf
*decoded_out
, const char *input
)
383 assert(decoded_out
&& input
);
385 len
= (int)strlen(input
);
386 git_buf_clear(decoded_out
);
388 for(i
= 0; i
< len
; i
++)
398 hi
= git__fromhex(input
[i
+ 1]);
399 lo
= git__fromhex(input
[i
+ 2]);
401 if (hi
< 0 || lo
< 0)
404 c
= (char)(hi
<< 4 | lo
);
408 if (git_buf_putc(decoded_out
, c
) < 0)
415 static int error_invalid_local_file_uri(const char *uri
)
417 giterr_set(GITERR_CONFIG
, "'%s' is not a valid local file URI", uri
);
421 static int local_file_url_prefixlen(const char *file_url
)
425 if (git__prefixcmp(file_url
, "file://") == 0) {
426 if (file_url
[7] == '/')
428 else if (git__prefixcmp(file_url
+ 7, "localhost/") == 0)
435 bool git_path_is_local_file_url(const char *file_url
)
437 return (local_file_url_prefixlen(file_url
) > 0);
440 int git_path_fromurl(git_buf
*local_path_out
, const char *file_url
)
444 assert(local_path_out
&& file_url
);
446 if ((offset
= local_file_url_prefixlen(file_url
)) < 0 ||
447 file_url
[offset
] == '\0' || file_url
[offset
] == '/')
448 return error_invalid_local_file_uri(file_url
);
451 offset
--; /* A *nix absolute path starts with a forward slash */
454 git_buf_clear(local_path_out
);
455 return git__percent_decode(local_path_out
, file_url
+ offset
);
458 int git_path_walk_up(
461 int (*cb
)(void *data
, const char *),
466 ssize_t stop
= 0, scan
;
471 if (ceiling
!= NULL
) {
472 if (git__prefixcmp(path
->ptr
, ceiling
) == 0)
473 stop
= (ssize_t
)strlen(ceiling
);
475 stop
= git_buf_len(path
);
477 scan
= git_buf_len(path
);
479 /* empty path: yield only once */
481 error
= cb(data
, "");
483 giterr_set_after_callback(error
);
487 iter
.ptr
= path
->ptr
;
488 iter
.size
= git_buf_len(path
);
489 iter
.asize
= path
->asize
;
491 while (scan
>= stop
) {
492 error
= cb(data
, iter
.ptr
);
493 iter
.ptr
[scan
] = oldc
;
496 giterr_set_after_callback(error
);
500 scan
= git_buf_rfind_next(&iter
, '/');
503 oldc
= iter
.ptr
[scan
];
505 iter
.ptr
[scan
] = '\0';
510 iter
.ptr
[scan
] = oldc
;
512 /* relative path: yield for the last component */
513 if (!error
&& stop
== 0 && iter
.ptr
[0] != '/') {
514 error
= cb(data
, "");
516 giterr_set_after_callback(error
);
522 bool git_path_exists(const char *path
)
525 return p_access(path
, F_OK
) == 0;
528 bool git_path_isdir(const char *path
)
531 if (p_stat(path
, &st
) < 0)
534 return S_ISDIR(st
.st_mode
) != 0;
537 bool git_path_isfile(const char *path
)
542 if (p_stat(path
, &st
) < 0)
545 return S_ISREG(st
.st_mode
) != 0;
548 bool git_path_islink(const char *path
)
553 if (p_lstat(path
, &st
) < 0)
556 return S_ISLNK(st
.st_mode
) != 0;
561 bool git_path_is_empty_dir(const char *path
)
563 git_win32_path filter_w
;
566 if (git_win32__findfirstfile_filter(filter_w
, path
)) {
567 WIN32_FIND_DATAW findData
;
568 HANDLE hFind
= FindFirstFileW(filter_w
, &findData
);
570 /* FindFirstFile will fail if there are no children to the given
571 * path, which can happen if the given path is a file (and obviously
572 * has no children) or if the given path is an empty mount point.
573 * (Most directories have at least directory entries '.' and '..',
574 * but ridiculously another volume mounted in another drive letter's
575 * path space do not, and thus have nothing to enumerate.) If
576 * FindFirstFile fails, check if this is a directory-like thing
579 if (hFind
== INVALID_HANDLE_VALUE
)
580 return git_path_isdir(path
);
582 /* If the find handle was created successfully, then it's a directory */
586 /* Allow the enumeration to return . and .. and still be considered
587 * empty. In the special case of drive roots (i.e. C:\) where . and
588 * .. do not occur, we can still consider the path to be an empty
589 * directory if there's nothing there. */
590 if (!git_path_is_dot_or_dotdotW(findData
.cFileName
)) {
594 } while (FindNextFileW(hFind
, &findData
));
604 static int path_found_entry(void *payload
, git_buf
*path
)
607 return !git_path_is_dot_or_dotdot(path
->ptr
);
610 bool git_path_is_empty_dir(const char *path
)
613 git_buf dir
= GIT_BUF_INIT
;
615 if (!git_path_isdir(path
))
618 if ((error
= git_buf_sets(&dir
, path
)) != 0)
621 error
= git_path_direach(&dir
, 0, path_found_entry
, NULL
);
630 int git_path_set_error(int errno_value
, const char *path
, const char *action
)
632 switch (errno_value
) {
635 giterr_set(GITERR_OS
, "Could not find '%s' to %s", path
, action
);
636 return GIT_ENOTFOUND
;
640 giterr_set(GITERR_OS
, "Invalid path for filesystem '%s'", path
);
641 return GIT_EINVALIDSPEC
;
644 giterr_set(GITERR_OS
, "Failed %s - '%s' already exists", action
, path
);
648 giterr_set(GITERR_OS
, "Failed %s - '%s' is locked", action
, path
);
652 giterr_set(GITERR_OS
, "Could not %s '%s'", action
, path
);
657 int git_path_lstat(const char *path
, struct stat
*st
)
659 if (p_lstat(path
, st
) == 0)
662 return git_path_set_error(errno
, path
, "stat");
665 static bool _check_dir_contents(
668 bool (*predicate
)(const char *))
671 size_t dir_size
= git_buf_len(dir
);
672 size_t sub_size
= strlen(sub
);
675 /* leave base valid even if we could not make space for subdir */
676 if (GIT_ADD_SIZET_OVERFLOW(&alloc_size
, dir_size
, sub_size
) ||
677 GIT_ADD_SIZET_OVERFLOW(&alloc_size
, alloc_size
, 2) ||
678 git_buf_try_grow(dir
, alloc_size
, false) < 0)
682 git_buf_joinpath(dir
, dir
->ptr
, sub
);
684 result
= predicate(dir
->ptr
);
687 git_buf_truncate(dir
, dir_size
);
691 bool git_path_contains(git_buf
*dir
, const char *item
)
693 return _check_dir_contents(dir
, item
, &git_path_exists
);
696 bool git_path_contains_dir(git_buf
*base
, const char *subdir
)
698 return _check_dir_contents(base
, subdir
, &git_path_isdir
);
701 bool git_path_contains_file(git_buf
*base
, const char *file
)
703 return _check_dir_contents(base
, file
, &git_path_isfile
);
706 int git_path_find_dir(git_buf
*dir
, const char *path
, const char *base
)
708 int error
= git_path_join_unrooted(dir
, path
, base
, NULL
);
711 char buf
[GIT_PATH_MAX
];
712 if (p_realpath(dir
->ptr
, buf
) != NULL
)
713 error
= git_buf_sets(dir
, buf
);
716 /* call dirname if this is not a directory */
717 if (!error
) /* && git_path_isdir(dir->ptr) == false) */
718 error
= (git_path_dirname_r(dir
, dir
->ptr
) < 0) ? -1 : 0;
721 error
= git_path_to_dir(dir
);
726 int git_path_resolve_relative(git_buf
*path
, size_t ceiling
)
728 char *base
, *to
, *from
, *next
;
731 GITERR_CHECK_ALLOC_BUF(path
);
733 if (ceiling
> path
->size
)
734 ceiling
= path
->size
;
736 /* recognize drive prefixes, etc. that should not be backed over */
738 ceiling
= git_path_root(path
->ptr
) + 1;
740 /* recognize URL prefixes that should not be backed over */
742 for (next
= path
->ptr
; *next
&& git__isalpha(*next
); ++next
);
743 if (next
[0] == ':' && next
[1] == '/' && next
[2] == '/')
744 ceiling
= (next
+ 3) - path
->ptr
;
747 base
= to
= from
= path
->ptr
+ ceiling
;
750 for (next
= from
; *next
&& *next
!= '/'; ++next
);
754 if (len
== 1 && from
[0] == '.')
755 /* do nothing with singleton dot */;
757 else if (len
== 2 && from
[0] == '.' && from
[1] == '.') {
758 /* error out if trying to up one from a hard base */
759 if (to
== base
&& ceiling
!= 0) {
760 giterr_set(GITERR_INVALID
,
761 "Cannot strip root component off url");
765 /* no more path segments to strip,
766 * use '../' as a new base path */
772 memmove(to
, from
, len
);
775 /* this is now the base, can't back up from a
779 /* back up a path segment */
780 while (to
> base
&& to
[-1] == '/') to
--;
781 while (to
> base
&& to
[-1] != '/') to
--;
784 if (*next
== '/' && *from
!= '/')
788 memmove(to
, from
, len
);
795 while (*from
== '/') from
++;
800 path
->size
= to
- path
->ptr
;
805 int git_path_apply_relative(git_buf
*target
, const char *relpath
)
807 git_buf_joinpath(target
, git_buf_cstr(target
), relpath
);
808 return git_path_resolve_relative(target
, 0);
812 const char *name1
, size_t len1
, int isdir1
,
813 const char *name2
, size_t len2
, int isdir2
,
814 int (*compare
)(const char *, const char *, size_t))
816 unsigned char c1
, c2
;
817 size_t len
= len1
< len2
? len1
: len2
;
820 cmp
= compare(name1
, name2
, len
);
827 if (c1
== '\0' && isdir1
)
830 if (c2
== '\0' && isdir2
)
833 return (c1
< c2
) ? -1 : (c1
> c2
) ? 1 : 0;
836 size_t git_path_common_dirlen(const char *one
, const char *two
)
838 const char *p
, *q
, *dirsep
= NULL
;
840 for (p
= one
, q
= two
; *p
&& *q
; p
++, q
++) {
841 if (*p
== '/' && *q
== '/')
847 return dirsep
? (dirsep
- one
) + 1 : 0;
850 int git_path_make_relative(git_buf
*path
, const char *parent
)
852 const char *p
, *q
, *p_dirsep
, *q_dirsep
;
853 size_t plen
= path
->size
, newlen
, alloclen
, depth
= 1, i
, offset
;
855 for (p_dirsep
= p
= path
->ptr
, q_dirsep
= q
= parent
; *p
&& *q
; p
++, q
++) {
856 if (*p
== '/' && *q
== '/') {
864 /* need at least 1 common path segment */
865 if ((p_dirsep
== path
->ptr
|| q_dirsep
== parent
) &&
866 (*p_dirsep
!= '/' || *q_dirsep
!= '/')) {
867 giterr_set(GITERR_INVALID
,
868 "%s is not a parent of %s", parent
, path
->ptr
);
869 return GIT_ENOTFOUND
;
872 if (*p
== '/' && !*q
)
874 else if (!*p
&& *q
== '/')
877 return git_buf_clear(path
), 0;
883 plen
-= (p
- path
->ptr
);
886 return git_buf_set(path
, p
, plen
);
888 for (; (q
= strchr(q
, '/')) && *(q
+ 1); q
++)
891 GITERR_CHECK_ALLOC_MULTIPLY(&newlen
, depth
, 3);
892 GITERR_CHECK_ALLOC_ADD(&newlen
, newlen
, plen
);
894 GITERR_CHECK_ALLOC_ADD(&alloclen
, newlen
, 1);
896 /* save the offset as we might realllocate the pointer */
897 offset
= p
- path
->ptr
;
898 if (git_buf_try_grow(path
, alloclen
, 1) < 0)
900 p
= path
->ptr
+ offset
;
902 memmove(path
->ptr
+ (depth
* 3), p
, plen
+ 1);
904 for (i
= 0; i
< depth
; i
++)
905 memcpy(path
->ptr
+ (i
* 3), "../", 3);
911 bool git_path_has_non_ascii(const char *path
, size_t pathlen
)
913 const uint8_t *scan
= (const uint8_t *)path
, *end
;
915 for (end
= scan
+ pathlen
; scan
< end
; ++scan
)
924 int git_path_iconv_init_precompose(git_path_iconv_t
*ic
)
926 git_buf_init(&ic
->buf
, 0);
927 ic
->map
= iconv_open(GIT_PATH_REPO_ENCODING
, GIT_PATH_NATIVE_ENCODING
);
931 void git_path_iconv_clear(git_path_iconv_t
*ic
)
934 if (ic
->map
!= (iconv_t
)-1)
935 iconv_close(ic
->map
);
936 git_buf_free(&ic
->buf
);
940 int git_path_iconv(git_path_iconv_t
*ic
, const char **in
, size_t *inlen
)
942 char *nfd
= (char*)*in
, *nfc
;
943 size_t nfdlen
= *inlen
, nfclen
, wantlen
= nfdlen
, alloclen
, rv
;
946 if (!ic
|| ic
->map
== (iconv_t
)-1 ||
947 !git_path_has_non_ascii(*in
, *inlen
))
950 git_buf_clear(&ic
->buf
);
953 GITERR_CHECK_ALLOC_ADD(&alloclen
, wantlen
, 1);
954 if (git_buf_grow(&ic
->buf
, alloclen
) < 0)
957 nfc
= ic
->buf
.ptr
+ ic
->buf
.size
;
958 nfclen
= ic
->buf
.asize
- ic
->buf
.size
;
960 rv
= iconv(ic
->map
, &nfd
, &nfdlen
, &nfc
, &nfclen
);
962 ic
->buf
.size
= (nfc
- ic
->buf
.ptr
);
964 if (rv
!= (size_t)-1)
967 /* if we cannot convert the data (probably because iconv thinks
968 * it is not valid UTF-8 source data), then use original data
973 /* make space for 2x the remaining data to be converted
974 * (with per retry overhead to avoid infinite loops)
976 wantlen
= ic
->buf
.size
+ max(nfclen
, nfdlen
) * 2 + (size_t)(retry
* 4);
982 ic
->buf
.ptr
[ic
->buf
.size
] = '\0';
985 *inlen
= ic
->buf
.size
;
990 giterr_set(GITERR_OS
, "Unable to convert unicode path data");
994 static const char *nfc_file
= "\xC3\x85\x73\x74\x72\xC3\xB6\x6D.XXXXXX";
995 static const char *nfd_file
= "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D.XXXXXX";
997 /* Check if the platform is decomposing unicode data for us. We will
998 * emulate core Git and prefer to use precomposed unicode data internally
999 * on these platforms, composing the decomposed unicode on the fly.
1001 * This mainly happens on the Mac where HDFS stores filenames as
1002 * decomposed unicode. Even on VFAT and SAMBA file systems, the Mac will
1003 * return decomposed unicode from readdir() even when the actual
1004 * filesystem is storing precomposed unicode.
1006 bool git_path_does_fs_decompose_unicode(const char *root
)
1008 git_buf path
= GIT_BUF_INIT
;
1010 bool found_decomposed
= false;
1013 /* Create a file using a precomposed path and then try to find it
1014 * using the decomposed name. If the lookup fails, then we will mark
1015 * that we should precompose unicode for this repository.
1017 if (git_buf_joinpath(&path
, root
, nfc_file
) < 0 ||
1018 (fd
= p_mkstemp(path
.ptr
)) < 0)
1022 /* record trailing digits generated by mkstemp */
1023 memcpy(tmp
, path
.ptr
+ path
.size
- sizeof(tmp
), sizeof(tmp
));
1025 /* try to look up as NFD path */
1026 if (git_buf_joinpath(&path
, root
, nfd_file
) < 0)
1028 memcpy(path
.ptr
+ path
.size
- sizeof(tmp
), tmp
, sizeof(tmp
));
1030 found_decomposed
= git_path_exists(path
.ptr
);
1032 /* remove temporary file (using original precomposed path) */
1033 if (git_buf_joinpath(&path
, root
, nfc_file
) < 0)
1035 memcpy(path
.ptr
+ path
.size
- sizeof(tmp
), tmp
, sizeof(tmp
));
1037 (void)p_unlink(path
.ptr
);
1040 git_buf_free(&path
);
1041 return found_decomposed
;
1046 bool git_path_does_fs_decompose_unicode(const char *root
)
1054 #if defined(__sun) || defined(__GNU__)
1055 typedef char path_dirent_data
[sizeof(struct dirent
) + FILENAME_MAX
+ 1];
1057 typedef struct dirent path_dirent_data
;
1060 int git_path_direach(
1063 int (*fn
)(void *, git_buf
*),
1071 #ifdef GIT_USE_ICONV
1072 git_path_iconv_t ic
= GIT_PATH_ICONV_INIT
;
1077 if (git_path_to_dir(path
) < 0)
1080 wd_len
= git_buf_len(path
);
1082 if ((dir
= opendir(path
->ptr
)) == NULL
) {
1083 giterr_set(GITERR_OS
, "Failed to open directory '%s'", path
->ptr
);
1084 if (errno
== ENOENT
)
1085 return GIT_ENOTFOUND
;
1090 #ifdef GIT_USE_ICONV
1091 if ((flags
& GIT_PATH_DIR_PRECOMPOSE_UNICODE
) != 0)
1092 (void)git_path_iconv_init_precompose(&ic
);
1095 while ((de
= readdir(dir
)) != NULL
) {
1096 const char *de_path
= de
->d_name
;
1097 size_t de_len
= strlen(de_path
);
1099 if (git_path_is_dot_or_dotdot(de_path
))
1102 #ifdef GIT_USE_ICONV
1103 if ((error
= git_path_iconv(&ic
, &de_path
, &de_len
)) < 0)
1107 if ((error
= git_buf_put(path
, de_path
, de_len
)) < 0)
1111 error
= fn(arg
, path
);
1113 git_buf_truncate(path
, wd_len
); /* restore path */
1115 /* Only set our own error if the callback did not set one already */
1118 giterr_set_after_callback(error
);
1126 #ifdef GIT_USE_ICONV
1127 git_path_iconv_clear(&ic
);
1133 #if defined(GIT_WIN32) && !defined(__MINGW32__)
1135 /* Using _FIND_FIRST_EX_LARGE_FETCH may increase performance in Windows 7
1138 #ifndef FIND_FIRST_EX_LARGE_FETCH
1139 # define FIND_FIRST_EX_LARGE_FETCH 2
1142 int git_path_diriter_init(
1143 git_path_diriter
*diriter
,
1147 git_win32_path path_filter
;
1149 static int is_win7_or_later
= -1;
1150 if (is_win7_or_later
< 0)
1151 is_win7_or_later
= git_has_win32_version(6, 1, 0);
1153 assert(diriter
&& path
);
1155 memset(diriter
, 0, sizeof(git_path_diriter
));
1156 diriter
->handle
= INVALID_HANDLE_VALUE
;
1158 if (git_buf_puts(&diriter
->path_utf8
, path
) < 0)
1161 git_path_trim_slashes(&diriter
->path_utf8
);
1163 if (diriter
->path_utf8
.size
== 0) {
1164 giterr_set(GITERR_FILESYSTEM
, "Could not open directory '%s'", path
);
1168 if ((diriter
->parent_len
= git_win32_path_from_utf8(diriter
->path
, diriter
->path_utf8
.ptr
)) < 0 ||
1169 !git_win32__findfirstfile_filter(path_filter
, diriter
->path_utf8
.ptr
)) {
1170 giterr_set(GITERR_OS
, "Could not parse the directory path '%s'", path
);
1174 diriter
->handle
= FindFirstFileExW(
1176 is_win7_or_later
? FindExInfoBasic
: FindExInfoStandard
,
1178 FindExSearchNameMatch
,
1180 is_win7_or_later
? FIND_FIRST_EX_LARGE_FETCH
: 0);
1182 if (diriter
->handle
== INVALID_HANDLE_VALUE
) {
1183 giterr_set(GITERR_OS
, "Could not open directory '%s'", path
);
1187 diriter
->parent_utf8_len
= diriter
->path_utf8
.size
;
1188 diriter
->flags
= flags
;
1192 static int diriter_update_paths(git_path_diriter
*diriter
)
1194 size_t filename_len
, path_len
;
1196 filename_len
= wcslen(diriter
->current
.cFileName
);
1198 if (GIT_ADD_SIZET_OVERFLOW(&path_len
, diriter
->parent_len
, filename_len
) ||
1199 GIT_ADD_SIZET_OVERFLOW(&path_len
, path_len
, 2))
1202 if (path_len
> GIT_WIN_PATH_UTF16
) {
1203 giterr_set(GITERR_FILESYSTEM
,
1204 "invalid path '%.*ls\\%ls' (path too long)",
1205 diriter
->parent_len
, diriter
->path
, diriter
->current
.cFileName
);
1209 diriter
->path
[diriter
->parent_len
] = L
'\\';
1210 memcpy(&diriter
->path
[diriter
->parent_len
+1],
1211 diriter
->current
.cFileName
, filename_len
* sizeof(wchar_t));
1212 diriter
->path
[path_len
-1] = L
'\0';
1214 git_buf_truncate(&diriter
->path_utf8
, diriter
->parent_utf8_len
);
1216 if (diriter
->parent_utf8_len
> 0 &&
1217 diriter
->path_utf8
.ptr
[diriter
->parent_utf8_len
-1] != '/')
1218 git_buf_putc(&diriter
->path_utf8
, '/');
1220 git_buf_put_w(&diriter
->path_utf8
, diriter
->current
.cFileName
, filename_len
);
1222 if (git_buf_oom(&diriter
->path_utf8
))
1228 int git_path_diriter_next(git_path_diriter
*diriter
)
1230 bool skip_dot
= !(diriter
->flags
& GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT
);
1233 /* Our first time through, we already have the data from
1234 * FindFirstFileW. Use it, otherwise get the next file.
1236 if (!diriter
->needs_next
)
1237 diriter
->needs_next
= 1;
1238 else if (!FindNextFileW(diriter
->handle
, &diriter
->current
))
1239 return GIT_ITEROVER
;
1240 } while (skip_dot
&& git_path_is_dot_or_dotdotW(diriter
->current
.cFileName
));
1242 if (diriter_update_paths(diriter
) < 0)
1248 int git_path_diriter_filename(
1251 git_path_diriter
*diriter
)
1253 assert(out
&& out_len
&& diriter
);
1255 assert(diriter
->path_utf8
.size
> diriter
->parent_utf8_len
);
1257 *out
= &diriter
->path_utf8
.ptr
[diriter
->parent_utf8_len
+1];
1258 *out_len
= diriter
->path_utf8
.size
- diriter
->parent_utf8_len
- 1;
1262 int git_path_diriter_fullpath(
1265 git_path_diriter
*diriter
)
1267 assert(out
&& out_len
&& diriter
);
1269 *out
= diriter
->path_utf8
.ptr
;
1270 *out_len
= diriter
->path_utf8
.size
;
1274 int git_path_diriter_stat(struct stat
*out
, git_path_diriter
*diriter
)
1276 assert(out
&& diriter
);
1278 return git_win32__file_attribute_to_stat(out
,
1279 (WIN32_FILE_ATTRIBUTE_DATA
*)&diriter
->current
,
1283 void git_path_diriter_free(git_path_diriter
*diriter
)
1285 if (diriter
== NULL
)
1288 git_buf_free(&diriter
->path_utf8
);
1290 if (diriter
->handle
!= INVALID_HANDLE_VALUE
) {
1291 FindClose(diriter
->handle
);
1292 diriter
->handle
= INVALID_HANDLE_VALUE
;
1298 int git_path_diriter_init(
1299 git_path_diriter
*diriter
,
1303 assert(diriter
&& path
);
1305 memset(diriter
, 0, sizeof(git_path_diriter
));
1307 if (git_buf_puts(&diriter
->path
, path
) < 0)
1310 git_path_trim_slashes(&diriter
->path
);
1312 if (diriter
->path
.size
== 0) {
1313 giterr_set(GITERR_FILESYSTEM
, "Could not open directory '%s'", path
);
1317 if ((diriter
->dir
= opendir(diriter
->path
.ptr
)) == NULL
) {
1318 git_buf_free(&diriter
->path
);
1320 giterr_set(GITERR_OS
, "Failed to open directory '%s'", path
);
1324 #ifdef GIT_USE_ICONV
1325 if ((flags
& GIT_PATH_DIR_PRECOMPOSE_UNICODE
) != 0)
1326 (void)git_path_iconv_init_precompose(&diriter
->ic
);
1329 diriter
->parent_len
= diriter
->path
.size
;
1330 diriter
->flags
= flags
;
1335 int git_path_diriter_next(git_path_diriter
*diriter
)
1338 const char *filename
;
1339 size_t filename_len
;
1340 bool skip_dot
= !(diriter
->flags
& GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT
);
1348 if ((de
= readdir(diriter
->dir
)) == NULL
) {
1350 return GIT_ITEROVER
;
1352 giterr_set(GITERR_OS
,
1353 "Could not read directory '%s'", diriter
->path
.ptr
);
1356 } while (skip_dot
&& git_path_is_dot_or_dotdot(de
->d_name
));
1358 filename
= de
->d_name
;
1359 filename_len
= strlen(filename
);
1361 #ifdef GIT_USE_ICONV
1362 if ((diriter
->flags
& GIT_PATH_DIR_PRECOMPOSE_UNICODE
) != 0 &&
1363 (error
= git_path_iconv(&diriter
->ic
, &filename
, &filename_len
)) < 0)
1367 git_buf_truncate(&diriter
->path
, diriter
->parent_len
);
1369 if (diriter
->parent_len
> 0 &&
1370 diriter
->path
.ptr
[diriter
->parent_len
-1] != '/')
1371 git_buf_putc(&diriter
->path
, '/');
1373 git_buf_put(&diriter
->path
, filename
, filename_len
);
1375 if (git_buf_oom(&diriter
->path
))
1381 int git_path_diriter_filename(
1384 git_path_diriter
*diriter
)
1386 assert(out
&& out_len
&& diriter
);
1388 assert(diriter
->path
.size
> diriter
->parent_len
);
1390 *out
= &diriter
->path
.ptr
[diriter
->parent_len
+1];
1391 *out_len
= diriter
->path
.size
- diriter
->parent_len
- 1;
1395 int git_path_diriter_fullpath(
1398 git_path_diriter
*diriter
)
1400 assert(out
&& out_len
&& diriter
);
1402 *out
= diriter
->path
.ptr
;
1403 *out_len
= diriter
->path
.size
;
1407 int git_path_diriter_stat(struct stat
*out
, git_path_diriter
*diriter
)
1409 assert(out
&& diriter
);
1411 return git_path_lstat(diriter
->path
.ptr
, out
);
1414 void git_path_diriter_free(git_path_diriter
*diriter
)
1416 if (diriter
== NULL
)
1420 closedir(diriter
->dir
);
1421 diriter
->dir
= NULL
;
1424 #ifdef GIT_USE_ICONV
1425 git_path_iconv_clear(&diriter
->ic
);
1428 git_buf_free(&diriter
->path
);
1433 int git_path_dirload(
1434 git_vector
*contents
,
1439 git_path_diriter iter
= GIT_PATH_DIRITER_INIT
;
1445 assert(contents
&& path
);
1447 if ((error
= git_path_diriter_init(&iter
, path
, flags
)) < 0)
1450 while ((error
= git_path_diriter_next(&iter
)) == 0) {
1451 if ((error
= git_path_diriter_fullpath(&name
, &name_len
, &iter
)) < 0)
1454 assert(name_len
> prefix_len
);
1456 dup
= git__strndup(name
+ prefix_len
, name_len
- prefix_len
);
1457 GITERR_CHECK_ALLOC(dup
);
1459 if ((error
= git_vector_insert(contents
, dup
)) < 0)
1463 if (error
== GIT_ITEROVER
)
1466 git_path_diriter_free(&iter
);
1470 int git_path_from_url_or_path(git_buf
*local_path_out
, const char *url_or_path
)
1472 if (git_path_is_local_file_url(url_or_path
))
1473 return git_path_fromurl(local_path_out
, url_or_path
);
1475 return git_buf_sets(local_path_out
, url_or_path
);
1478 /* Reject paths like AUX or COM1, or those versions that end in a dot or
1479 * colon. ("AUX." or "AUX:")
1481 GIT_INLINE(bool) verify_dospath(
1482 const char *component
,
1484 const char dospath
[3],
1487 size_t last
= trailing_num
? 4 : 3;
1489 if (len
< last
|| git__strncasecmp(component
, dospath
, 3) != 0)
1492 if (trailing_num
&& (component
[3] < '1' || component
[3] > '9'))
1495 return (len
> last
&&
1496 component
[last
] != '.' &&
1497 component
[last
] != ':');
1500 static int32_t next_hfs_char(const char **in
, size_t *len
)
1504 int cp_len
= git__utf8_iterate((const uint8_t *)(*in
), (int)(*len
), &codepoint
);
1511 /* these code points are ignored completely */
1512 switch (codepoint
) {
1513 case 0x200c: /* ZERO WIDTH NON-JOINER */
1514 case 0x200d: /* ZERO WIDTH JOINER */
1515 case 0x200e: /* LEFT-TO-RIGHT MARK */
1516 case 0x200f: /* RIGHT-TO-LEFT MARK */
1517 case 0x202a: /* LEFT-TO-RIGHT EMBEDDING */
1518 case 0x202b: /* RIGHT-TO-LEFT EMBEDDING */
1519 case 0x202c: /* POP DIRECTIONAL FORMATTING */
1520 case 0x202d: /* LEFT-TO-RIGHT OVERRIDE */
1521 case 0x202e: /* RIGHT-TO-LEFT OVERRIDE */
1522 case 0x206a: /* INHIBIT SYMMETRIC SWAPPING */
1523 case 0x206b: /* ACTIVATE SYMMETRIC SWAPPING */
1524 case 0x206c: /* INHIBIT ARABIC FORM SHAPING */
1525 case 0x206d: /* ACTIVATE ARABIC FORM SHAPING */
1526 case 0x206e: /* NATIONAL DIGIT SHAPES */
1527 case 0x206f: /* NOMINAL DIGIT SHAPES */
1528 case 0xfeff: /* ZERO WIDTH NO-BREAK SPACE */
1532 /* fold into lowercase -- this will only fold characters in
1533 * the ASCII range, which is perfectly fine, because the
1534 * git folder name can only be composed of ascii characters
1536 return git__tolower(codepoint
);
1538 return 0; /* NULL byte -- end of string */
1541 static bool verify_dotgit_hfs(const char *path
, size_t len
)
1543 if (next_hfs_char(&path
, &len
) != '.' ||
1544 next_hfs_char(&path
, &len
) != 'g' ||
1545 next_hfs_char(&path
, &len
) != 'i' ||
1546 next_hfs_char(&path
, &len
) != 't' ||
1547 next_hfs_char(&path
, &len
) != 0)
1553 GIT_INLINE(bool) verify_dotgit_ntfs(git_repository
*repo
, const char *path
, size_t len
)
1555 git_buf
*reserved
= git_repository__reserved_names_win32
;
1556 size_t reserved_len
= git_repository__reserved_names_win32_len
;
1557 size_t start
= 0, i
;
1560 git_repository__reserved_names(&reserved
, &reserved_len
, repo
, true);
1562 for (i
= 0; i
< reserved_len
; i
++) {
1563 git_buf
*r
= &reserved
[i
];
1565 if (len
>= r
->size
&&
1566 strncasecmp(path
, r
->ptr
, r
->size
) == 0) {
1575 /* Reject paths like ".git\" */
1576 if (path
[start
] == '\\')
1579 /* Reject paths like '.git ' or '.git.' */
1580 for (i
= start
; i
< len
; i
++) {
1581 if (path
[i
] != ' ' && path
[i
] != '.')
1588 GIT_INLINE(bool) verify_char(unsigned char c
, unsigned int flags
)
1590 if ((flags
& GIT_PATH_REJECT_BACKSLASH
) && c
== '\\')
1593 if ((flags
& GIT_PATH_REJECT_SLASH
) && c
== '/')
1596 if (flags
& GIT_PATH_REJECT_NT_CHARS
) {
1616 * We fundamentally don't like some paths when dealing with user-inputted
1617 * strings (in checkout or ref names): we don't want dot or dot-dot
1618 * anywhere, we want to avoid writing weird paths on Windows that can't
1619 * be handled by tools that use the non-\\?\ APIs, we don't want slashes
1620 * or double slashes at the end of paths that can make them ambiguous.
1622 * For checkout, we don't want to recurse into ".git" either.
1624 static bool verify_component(
1625 git_repository
*repo
,
1626 const char *component
,
1633 if ((flags
& GIT_PATH_REJECT_TRAVERSAL
) &&
1634 len
== 1 && component
[0] == '.')
1637 if ((flags
& GIT_PATH_REJECT_TRAVERSAL
) &&
1638 len
== 2 && component
[0] == '.' && component
[1] == '.')
1641 if ((flags
& GIT_PATH_REJECT_TRAILING_DOT
) && component
[len
-1] == '.')
1644 if ((flags
& GIT_PATH_REJECT_TRAILING_SPACE
) && component
[len
-1] == ' ')
1647 if ((flags
& GIT_PATH_REJECT_TRAILING_COLON
) && component
[len
-1] == ':')
1650 if (flags
& GIT_PATH_REJECT_DOS_PATHS
) {
1651 if (!verify_dospath(component
, len
, "CON", false) ||
1652 !verify_dospath(component
, len
, "PRN", false) ||
1653 !verify_dospath(component
, len
, "AUX", false) ||
1654 !verify_dospath(component
, len
, "NUL", false) ||
1655 !verify_dospath(component
, len
, "COM", true) ||
1656 !verify_dospath(component
, len
, "LPT", true))
1660 if (flags
& GIT_PATH_REJECT_DOT_GIT_HFS
&&
1661 !verify_dotgit_hfs(component
, len
))
1664 if (flags
& GIT_PATH_REJECT_DOT_GIT_NTFS
&&
1665 !verify_dotgit_ntfs(repo
, component
, len
))
1668 /* don't bother rerunning the `.git` test if we ran the HFS or NTFS
1669 * specific tests, they would have already rejected `.git`.
1671 if ((flags
& GIT_PATH_REJECT_DOT_GIT_HFS
) == 0 &&
1672 (flags
& GIT_PATH_REJECT_DOT_GIT_NTFS
) == 0 &&
1673 (flags
& GIT_PATH_REJECT_DOT_GIT_LITERAL
) &&
1675 component
[0] == '.' &&
1676 (component
[1] == 'g' || component
[1] == 'G') &&
1677 (component
[2] == 'i' || component
[2] == 'I') &&
1678 (component
[3] == 't' || component
[3] == 'T'))
1684 GIT_INLINE(unsigned int) dotgit_flags(
1685 git_repository
*repo
,
1688 int protectHFS
= 0, protectNTFS
= 0;
1690 flags
|= GIT_PATH_REJECT_DOT_GIT_LITERAL
;
1700 if (repo
&& !protectHFS
)
1701 git_repository__cvar(&protectHFS
, repo
, GIT_CVAR_PROTECTHFS
);
1703 flags
|= GIT_PATH_REJECT_DOT_GIT_HFS
;
1705 if (repo
&& !protectNTFS
)
1706 git_repository__cvar(&protectNTFS
, repo
, GIT_CVAR_PROTECTNTFS
);
1708 flags
|= GIT_PATH_REJECT_DOT_GIT_NTFS
;
1713 bool git_path_isvalid(
1714 git_repository
*repo
,
1718 const char *start
, *c
;
1720 /* Upgrade the ".git" checks based on platform */
1721 if ((flags
& GIT_PATH_REJECT_DOT_GIT
))
1722 flags
= dotgit_flags(repo
, flags
);
1724 for (start
= c
= path
; *c
; c
++) {
1725 if (!verify_char(*c
, flags
))
1729 if (!verify_component(repo
, start
, (c
- start
), flags
))
1736 return verify_component(repo
, start
, (c
- start
), flags
);
1739 int git_path_normalize_slashes(git_buf
*out
, const char *path
)
1744 if ((error
= git_buf_puts(out
, path
)) < 0)
1747 for (p
= out
->ptr
; *p
; p
++) {