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 int git_path_prettify(git_buf
*path_out
, const char *path
, const char *base
)
311 char buf
[GIT_PATH_MAX
];
313 assert(path
&& path_out
);
315 /* construct path if needed */
316 if (base
!= NULL
&& git_path_root(path
) < 0) {
317 if (git_buf_joinpath(path_out
, base
, path
) < 0)
319 path
= path_out
->ptr
;
322 if (p_realpath(path
, buf
) == NULL
) {
323 /* giterr_set resets the errno when dealing with a GITERR_OS kind of error */
324 int error
= (errno
== ENOENT
|| errno
== ENOTDIR
) ? GIT_ENOTFOUND
: -1;
325 giterr_set(GITERR_OS
, "Failed to resolve path '%s'", path
);
327 git_buf_clear(path_out
);
332 return git_buf_sets(path_out
, buf
);
335 int git_path_prettify_dir(git_buf
*path_out
, const char *path
, const char *base
)
337 int error
= git_path_prettify(path_out
, path
, base
);
338 return (error
< 0) ? error
: git_path_to_dir(path_out
);
341 int git_path_to_dir(git_buf
*path
)
343 if (path
->asize
> 0 &&
344 git_buf_len(path
) > 0 &&
345 path
->ptr
[git_buf_len(path
) - 1] != '/')
346 git_buf_putc(path
, '/');
348 return git_buf_oom(path
) ? -1 : 0;
351 void git_path_string_to_dir(char* path
, size_t size
)
353 size_t end
= strlen(path
);
355 if (end
&& path
[end
- 1] != '/' && end
< size
) {
357 path
[end
+ 1] = '\0';
361 int git__percent_decode(git_buf
*decoded_out
, const char *input
)
364 assert(decoded_out
&& input
);
366 len
= (int)strlen(input
);
367 git_buf_clear(decoded_out
);
369 for(i
= 0; i
< len
; i
++)
379 hi
= git__fromhex(input
[i
+ 1]);
380 lo
= git__fromhex(input
[i
+ 2]);
382 if (hi
< 0 || lo
< 0)
385 c
= (char)(hi
<< 4 | lo
);
389 if (git_buf_putc(decoded_out
, c
) < 0)
396 static int error_invalid_local_file_uri(const char *uri
)
398 giterr_set(GITERR_CONFIG
, "'%s' is not a valid local file URI", uri
);
402 static int local_file_url_prefixlen(const char *file_url
)
406 if (git__prefixcmp(file_url
, "file://") == 0) {
407 if (file_url
[7] == '/')
409 else if (git__prefixcmp(file_url
+ 7, "localhost/") == 0)
416 bool git_path_is_local_file_url(const char *file_url
)
418 return (local_file_url_prefixlen(file_url
) > 0);
421 int git_path_fromurl(git_buf
*local_path_out
, const char *file_url
)
425 assert(local_path_out
&& file_url
);
427 if ((offset
= local_file_url_prefixlen(file_url
)) < 0 ||
428 file_url
[offset
] == '\0' || file_url
[offset
] == '/')
429 return error_invalid_local_file_uri(file_url
);
432 offset
--; /* A *nix absolute path starts with a forward slash */
435 git_buf_clear(local_path_out
);
436 return git__percent_decode(local_path_out
, file_url
+ offset
);
439 int git_path_walk_up(
442 int (*cb
)(void *data
, const char *),
447 ssize_t stop
= 0, scan
;
452 if (ceiling
!= NULL
) {
453 if (git__prefixcmp(path
->ptr
, ceiling
) == 0)
454 stop
= (ssize_t
)strlen(ceiling
);
456 stop
= git_buf_len(path
);
458 scan
= git_buf_len(path
);
460 /* empty path: yield only once */
462 error
= cb(data
, "");
464 giterr_set_after_callback(error
);
468 iter
.ptr
= path
->ptr
;
469 iter
.size
= git_buf_len(path
);
470 iter
.asize
= path
->asize
;
472 while (scan
>= stop
) {
473 error
= cb(data
, iter
.ptr
);
474 iter
.ptr
[scan
] = oldc
;
477 giterr_set_after_callback(error
);
481 scan
= git_buf_rfind_next(&iter
, '/');
484 oldc
= iter
.ptr
[scan
];
486 iter
.ptr
[scan
] = '\0';
491 iter
.ptr
[scan
] = oldc
;
493 /* relative path: yield for the last component */
494 if (!error
&& stop
== 0 && iter
.ptr
[0] != '/') {
495 error
= cb(data
, "");
497 giterr_set_after_callback(error
);
503 bool git_path_exists(const char *path
)
506 return p_access(path
, F_OK
) == 0;
509 bool git_path_isdir(const char *path
)
512 if (p_stat(path
, &st
) < 0)
515 return S_ISDIR(st
.st_mode
) != 0;
518 bool git_path_isfile(const char *path
)
523 if (p_stat(path
, &st
) < 0)
526 return S_ISREG(st
.st_mode
) != 0;
531 bool git_path_is_empty_dir(const char *path
)
533 git_win32_path filter_w
;
536 if (git_win32__findfirstfile_filter(filter_w
, path
)) {
537 WIN32_FIND_DATAW findData
;
538 HANDLE hFind
= FindFirstFileW(filter_w
, &findData
);
540 /* FindFirstFile will fail if there are no children to the given
541 * path, which can happen if the given path is a file (and obviously
542 * has no children) or if the given path is an empty mount point.
543 * (Most directories have at least directory entries '.' and '..',
544 * but ridiculously another volume mounted in another drive letter's
545 * path space do not, and thus have nothing to enumerate.) If
546 * FindFirstFile fails, check if this is a directory-like thing
549 if (hFind
== INVALID_HANDLE_VALUE
)
550 return git_path_isdir(path
);
552 /* If the find handle was created successfully, then it's a directory */
556 /* Allow the enumeration to return . and .. and still be considered
557 * empty. In the special case of drive roots (i.e. C:\) where . and
558 * .. do not occur, we can still consider the path to be an empty
559 * directory if there's nothing there. */
560 if (!git_path_is_dot_or_dotdotW(findData
.cFileName
)) {
564 } while (FindNextFileW(hFind
, &findData
));
574 static int path_found_entry(void *payload
, git_buf
*path
)
577 return !git_path_is_dot_or_dotdot(path
->ptr
);
580 bool git_path_is_empty_dir(const char *path
)
583 git_buf dir
= GIT_BUF_INIT
;
585 if (!git_path_isdir(path
))
588 if ((error
= git_buf_sets(&dir
, path
)) != 0)
591 error
= git_path_direach(&dir
, 0, path_found_entry
, NULL
);
600 int git_path_set_error(int errno_value
, const char *path
, const char *action
)
602 switch (errno_value
) {
605 giterr_set(GITERR_OS
, "Could not find '%s' to %s", path
, action
);
606 return GIT_ENOTFOUND
;
610 giterr_set(GITERR_OS
, "Invalid path for filesystem '%s'", path
);
611 return GIT_EINVALIDSPEC
;
614 giterr_set(GITERR_OS
, "Failed %s - '%s' already exists", action
, path
);
618 giterr_set(GITERR_OS
, "Could not %s '%s'", action
, path
);
623 int git_path_lstat(const char *path
, struct stat
*st
)
625 if (p_lstat(path
, st
) == 0)
628 return git_path_set_error(errno
, path
, "stat");
631 static bool _check_dir_contents(
634 bool (*predicate
)(const char *))
637 size_t dir_size
= git_buf_len(dir
);
638 size_t sub_size
= strlen(sub
);
641 /* leave base valid even if we could not make space for subdir */
642 if (GIT_ADD_SIZET_OVERFLOW(&alloc_size
, dir_size
, sub_size
) ||
643 GIT_ADD_SIZET_OVERFLOW(&alloc_size
, alloc_size
, 2) ||
644 git_buf_try_grow(dir
, alloc_size
, false) < 0)
648 git_buf_joinpath(dir
, dir
->ptr
, sub
);
650 result
= predicate(dir
->ptr
);
653 git_buf_truncate(dir
, dir_size
);
657 bool git_path_contains(git_buf
*dir
, const char *item
)
659 return _check_dir_contents(dir
, item
, &git_path_exists
);
662 bool git_path_contains_dir(git_buf
*base
, const char *subdir
)
664 return _check_dir_contents(base
, subdir
, &git_path_isdir
);
667 bool git_path_contains_file(git_buf
*base
, const char *file
)
669 return _check_dir_contents(base
, file
, &git_path_isfile
);
672 int git_path_find_dir(git_buf
*dir
, const char *path
, const char *base
)
674 int error
= git_path_join_unrooted(dir
, path
, base
, NULL
);
677 char buf
[GIT_PATH_MAX
];
678 if (p_realpath(dir
->ptr
, buf
) != NULL
)
679 error
= git_buf_sets(dir
, buf
);
682 /* call dirname if this is not a directory */
683 if (!error
) /* && git_path_isdir(dir->ptr) == false) */
684 error
= (git_path_dirname_r(dir
, dir
->ptr
) < 0) ? -1 : 0;
687 error
= git_path_to_dir(dir
);
692 int git_path_resolve_relative(git_buf
*path
, size_t ceiling
)
694 char *base
, *to
, *from
, *next
;
697 if (!path
|| git_buf_oom(path
))
700 if (ceiling
> path
->size
)
701 ceiling
= path
->size
;
703 /* recognize drive prefixes, etc. that should not be backed over */
705 ceiling
= git_path_root(path
->ptr
) + 1;
707 /* recognize URL prefixes that should not be backed over */
709 for (next
= path
->ptr
; *next
&& git__isalpha(*next
); ++next
);
710 if (next
[0] == ':' && next
[1] == '/' && next
[2] == '/')
711 ceiling
= (next
+ 3) - path
->ptr
;
714 base
= to
= from
= path
->ptr
+ ceiling
;
717 for (next
= from
; *next
&& *next
!= '/'; ++next
);
721 if (len
== 1 && from
[0] == '.')
722 /* do nothing with singleton dot */;
724 else if (len
== 2 && from
[0] == '.' && from
[1] == '.') {
725 /* error out if trying to up one from a hard base */
726 if (to
== base
&& ceiling
!= 0) {
727 giterr_set(GITERR_INVALID
,
728 "Cannot strip root component off url");
732 /* no more path segments to strip,
733 * use '../' as a new base path */
739 memmove(to
, from
, len
);
742 /* this is now the base, can't back up from a
746 /* back up a path segment */
747 while (to
> base
&& to
[-1] == '/') to
--;
748 while (to
> base
&& to
[-1] != '/') to
--;
751 if (*next
== '/' && *from
!= '/')
755 memmove(to
, from
, len
);
762 while (*from
== '/') from
++;
767 path
->size
= to
- path
->ptr
;
772 int git_path_apply_relative(git_buf
*target
, const char *relpath
)
774 git_buf_joinpath(target
, git_buf_cstr(target
), relpath
);
775 return git_path_resolve_relative(target
, 0);
779 const char *name1
, size_t len1
, int isdir1
,
780 const char *name2
, size_t len2
, int isdir2
,
781 int (*compare
)(const char *, const char *, size_t))
783 unsigned char c1
, c2
;
784 size_t len
= len1
< len2
? len1
: len2
;
787 cmp
= compare(name1
, name2
, len
);
794 if (c1
== '\0' && isdir1
)
797 if (c2
== '\0' && isdir2
)
800 return (c1
< c2
) ? -1 : (c1
> c2
) ? 1 : 0;
803 int git_path_make_relative(git_buf
*path
, const char *parent
)
805 const char *p
, *q
, *p_dirsep
, *q_dirsep
;
806 size_t plen
= path
->size
, newlen
, alloclen
, depth
= 1, i
, offset
;
808 for (p_dirsep
= p
= path
->ptr
, q_dirsep
= q
= parent
; *p
&& *q
; p
++, q
++) {
809 if (*p
== '/' && *q
== '/') {
817 /* need at least 1 common path segment */
818 if ((p_dirsep
== path
->ptr
|| q_dirsep
== parent
) &&
819 (*p_dirsep
!= '/' || *q_dirsep
!= '/')) {
820 giterr_set(GITERR_INVALID
,
821 "%s is not a parent of %s", parent
, path
->ptr
);
822 return GIT_ENOTFOUND
;
825 if (*p
== '/' && !*q
)
827 else if (!*p
&& *q
== '/')
830 return git_buf_clear(path
), 0;
836 plen
-= (p
- path
->ptr
);
839 return git_buf_set(path
, p
, plen
);
841 for (; (q
= strchr(q
, '/')) && *(q
+ 1); q
++)
844 GITERR_CHECK_ALLOC_MULTIPLY(&newlen
, depth
, 3);
845 GITERR_CHECK_ALLOC_ADD(&newlen
, newlen
, plen
);
847 GITERR_CHECK_ALLOC_ADD(&alloclen
, newlen
, 1);
849 /* save the offset as we might realllocate the pointer */
850 offset
= p
- path
->ptr
;
851 if (git_buf_try_grow(path
, alloclen
, 1) < 0)
853 p
= path
->ptr
+ offset
;
855 memmove(path
->ptr
+ (depth
* 3), p
, plen
+ 1);
857 for (i
= 0; i
< depth
; i
++)
858 memcpy(path
->ptr
+ (i
* 3), "../", 3);
864 bool git_path_has_non_ascii(const char *path
, size_t pathlen
)
866 const uint8_t *scan
= (const uint8_t *)path
, *end
;
868 for (end
= scan
+ pathlen
; scan
< end
; ++scan
)
877 int git_path_iconv_init_precompose(git_path_iconv_t
*ic
)
879 git_buf_init(&ic
->buf
, 0);
880 ic
->map
= iconv_open(GIT_PATH_REPO_ENCODING
, GIT_PATH_NATIVE_ENCODING
);
884 void git_path_iconv_clear(git_path_iconv_t
*ic
)
887 if (ic
->map
!= (iconv_t
)-1)
888 iconv_close(ic
->map
);
889 git_buf_free(&ic
->buf
);
893 int git_path_iconv(git_path_iconv_t
*ic
, const char **in
, size_t *inlen
)
895 char *nfd
= (char*)*in
, *nfc
;
896 size_t nfdlen
= *inlen
, nfclen
, wantlen
= nfdlen
, alloclen
, rv
;
899 if (!ic
|| ic
->map
== (iconv_t
)-1 ||
900 !git_path_has_non_ascii(*in
, *inlen
))
903 git_buf_clear(&ic
->buf
);
906 GITERR_CHECK_ALLOC_ADD(&alloclen
, wantlen
, 1);
907 if (git_buf_grow(&ic
->buf
, alloclen
) < 0)
910 nfc
= ic
->buf
.ptr
+ ic
->buf
.size
;
911 nfclen
= ic
->buf
.asize
- ic
->buf
.size
;
913 rv
= iconv(ic
->map
, &nfd
, &nfdlen
, &nfc
, &nfclen
);
915 ic
->buf
.size
= (nfc
- ic
->buf
.ptr
);
917 if (rv
!= (size_t)-1)
920 /* if we cannot convert the data (probably because iconv thinks
921 * it is not valid UTF-8 source data), then use original data
926 /* make space for 2x the remaining data to be converted
927 * (with per retry overhead to avoid infinite loops)
929 wantlen
= ic
->buf
.size
+ max(nfclen
, nfdlen
) * 2 + (size_t)(retry
* 4);
935 ic
->buf
.ptr
[ic
->buf
.size
] = '\0';
938 *inlen
= ic
->buf
.size
;
943 giterr_set(GITERR_OS
, "Unable to convert unicode path data");
947 static const char *nfc_file
= "\xC3\x85\x73\x74\x72\xC3\xB6\x6D.XXXXXX";
948 static const char *nfd_file
= "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D.XXXXXX";
950 /* Check if the platform is decomposing unicode data for us. We will
951 * emulate core Git and prefer to use precomposed unicode data internally
952 * on these platforms, composing the decomposed unicode on the fly.
954 * This mainly happens on the Mac where HDFS stores filenames as
955 * decomposed unicode. Even on VFAT and SAMBA file systems, the Mac will
956 * return decomposed unicode from readdir() even when the actual
957 * filesystem is storing precomposed unicode.
959 bool git_path_does_fs_decompose_unicode(const char *root
)
961 git_buf path
= GIT_BUF_INIT
;
963 bool found_decomposed
= false;
966 /* Create a file using a precomposed path and then try to find it
967 * using the decomposed name. If the lookup fails, then we will mark
968 * that we should precompose unicode for this repository.
970 if (git_buf_joinpath(&path
, root
, nfc_file
) < 0 ||
971 (fd
= p_mkstemp(path
.ptr
)) < 0)
975 /* record trailing digits generated by mkstemp */
976 memcpy(tmp
, path
.ptr
+ path
.size
- sizeof(tmp
), sizeof(tmp
));
978 /* try to look up as NFD path */
979 if (git_buf_joinpath(&path
, root
, nfd_file
) < 0)
981 memcpy(path
.ptr
+ path
.size
- sizeof(tmp
), tmp
, sizeof(tmp
));
983 found_decomposed
= git_path_exists(path
.ptr
);
985 /* remove temporary file (using original precomposed path) */
986 if (git_buf_joinpath(&path
, root
, nfc_file
) < 0)
988 memcpy(path
.ptr
+ path
.size
- sizeof(tmp
), tmp
, sizeof(tmp
));
990 (void)p_unlink(path
.ptr
);
994 return found_decomposed
;
999 bool git_path_does_fs_decompose_unicode(const char *root
)
1007 #if defined(__sun) || defined(__GNU__)
1008 typedef char path_dirent_data
[sizeof(struct dirent
) + FILENAME_MAX
+ 1];
1010 typedef struct dirent path_dirent_data
;
1013 int git_path_direach(
1016 int (*fn
)(void *, git_buf
*),
1024 #ifdef GIT_USE_ICONV
1025 git_path_iconv_t ic
= GIT_PATH_ICONV_INIT
;
1030 if (git_path_to_dir(path
) < 0)
1033 wd_len
= git_buf_len(path
);
1035 if ((dir
= opendir(path
->ptr
)) == NULL
) {
1036 giterr_set(GITERR_OS
, "Failed to open directory '%s'", path
->ptr
);
1037 if (errno
== ENOENT
)
1038 return GIT_ENOTFOUND
;
1043 #ifdef GIT_USE_ICONV
1044 if ((flags
& GIT_PATH_DIR_PRECOMPOSE_UNICODE
) != 0)
1045 (void)git_path_iconv_init_precompose(&ic
);
1048 while ((de
= readdir(dir
)) != NULL
) {
1049 const char *de_path
= de
->d_name
;
1050 size_t de_len
= strlen(de_path
);
1052 if (git_path_is_dot_or_dotdot(de_path
))
1055 #ifdef GIT_USE_ICONV
1056 if ((error
= git_path_iconv(&ic
, &de_path
, &de_len
)) < 0)
1060 if ((error
= git_buf_put(path
, de_path
, de_len
)) < 0)
1064 error
= fn(arg
, path
);
1066 git_buf_truncate(path
, wd_len
); /* restore path */
1068 /* Only set our own error if the callback did not set one already */
1071 giterr_set_after_callback(error
);
1079 #ifdef GIT_USE_ICONV
1080 git_path_iconv_clear(&ic
);
1086 #if defined(GIT_WIN32) && !defined(__MINGW32__)
1088 /* Using _FIND_FIRST_EX_LARGE_FETCH may increase performance in Windows 7
1091 #ifndef FIND_FIRST_EX_LARGE_FETCH
1092 # define FIND_FIRST_EX_LARGE_FETCH 2
1095 int git_path_diriter_init(
1096 git_path_diriter
*diriter
,
1100 git_win32_path path_filter
;
1103 static int is_win7_or_later
= -1;
1104 if (is_win7_or_later
< 0)
1105 is_win7_or_later
= git_has_win32_version(6, 1, 0);
1107 assert(diriter
&& path
);
1109 memset(diriter
, 0, sizeof(git_path_diriter
));
1110 diriter
->handle
= INVALID_HANDLE_VALUE
;
1112 if (git_buf_puts(&diriter
->path_utf8
, path
) < 0)
1115 git_path_trim_slashes(&diriter
->path_utf8
);
1117 if (diriter
->path_utf8
.size
== 0) {
1118 giterr_set(GITERR_FILESYSTEM
, "Could not open directory '%s'", path
);
1122 if ((diriter
->parent_len
= git_win32_path_from_utf8(diriter
->path
, diriter
->path_utf8
.ptr
)) < 0 ||
1123 !git_win32__findfirstfile_filter(path_filter
, diriter
->path_utf8
.ptr
)) {
1124 giterr_set(GITERR_OS
, "Could not parse the directory path '%s'", path
);
1128 diriter
->handle
= FindFirstFileExW(
1130 is_win7_or_later
? FindExInfoBasic
: FindExInfoStandard
,
1132 FindExSearchNameMatch
,
1134 is_win7_or_later
? FIND_FIRST_EX_LARGE_FETCH
: 0);
1136 if (diriter
->handle
== INVALID_HANDLE_VALUE
) {
1137 giterr_set(GITERR_OS
, "Could not open directory '%s'", path
);
1141 diriter
->parent_utf8_len
= diriter
->path_utf8
.size
;
1142 diriter
->flags
= flags
;
1146 static int diriter_update_paths(git_path_diriter
*diriter
)
1148 size_t filename_len
, path_len
;
1150 filename_len
= wcslen(diriter
->current
.cFileName
);
1152 if (GIT_ADD_SIZET_OVERFLOW(&path_len
, diriter
->parent_len
, filename_len
) ||
1153 GIT_ADD_SIZET_OVERFLOW(&path_len
, path_len
, 2))
1156 if (path_len
> GIT_WIN_PATH_UTF16
) {
1157 giterr_set(GITERR_FILESYSTEM
,
1158 "invalid path '%.*ls\\%ls' (path too long)",
1159 diriter
->parent_len
, diriter
->path
, diriter
->current
.cFileName
);
1163 diriter
->path
[diriter
->parent_len
] = L
'\\';
1164 memcpy(&diriter
->path
[diriter
->parent_len
+1],
1165 diriter
->current
.cFileName
, filename_len
* sizeof(wchar_t));
1166 diriter
->path
[path_len
-1] = L
'\0';
1168 git_buf_truncate(&diriter
->path_utf8
, diriter
->parent_utf8_len
);
1170 if (diriter
->parent_utf8_len
> 0 &&
1171 diriter
->path_utf8
.ptr
[diriter
->parent_utf8_len
-1] != '/')
1172 git_buf_putc(&diriter
->path_utf8
, '/');
1174 git_buf_put_w(&diriter
->path_utf8
, diriter
->current
.cFileName
, filename_len
);
1176 if (git_buf_oom(&diriter
->path_utf8
))
1182 int git_path_diriter_next(git_path_diriter
*diriter
)
1184 bool skip_dot
= !(diriter
->flags
& GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT
);
1187 /* Our first time through, we already have the data from
1188 * FindFirstFileW. Use it, otherwise get the next file.
1190 if (!diriter
->needs_next
)
1191 diriter
->needs_next
= 1;
1192 else if (!FindNextFileW(diriter
->handle
, &diriter
->current
))
1193 return GIT_ITEROVER
;
1194 } while (skip_dot
&& git_path_is_dot_or_dotdotW(diriter
->current
.cFileName
));
1196 if (diriter_update_paths(diriter
) < 0)
1202 int git_path_diriter_filename(
1205 git_path_diriter
*diriter
)
1207 assert(out
&& out_len
&& diriter
);
1209 assert(diriter
->path_utf8
.size
> diriter
->parent_utf8_len
);
1211 *out
= &diriter
->path_utf8
.ptr
[diriter
->parent_utf8_len
+1];
1212 *out_len
= diriter
->path_utf8
.size
- diriter
->parent_utf8_len
- 1;
1216 int git_path_diriter_fullpath(
1219 git_path_diriter
*diriter
)
1221 assert(out
&& out_len
&& diriter
);
1223 *out
= diriter
->path_utf8
.ptr
;
1224 *out_len
= diriter
->path_utf8
.size
;
1228 int git_path_diriter_stat(struct stat
*out
, git_path_diriter
*diriter
)
1230 assert(out
&& diriter
);
1232 return git_win32__file_attribute_to_stat(out
,
1233 (WIN32_FILE_ATTRIBUTE_DATA
*)&diriter
->current
,
1237 void git_path_diriter_free(git_path_diriter
*diriter
)
1239 if (diriter
== NULL
)
1242 git_buf_free(&diriter
->path_utf8
);
1244 if (diriter
->handle
!= INVALID_HANDLE_VALUE
) {
1245 FindClose(diriter
->handle
);
1246 diriter
->handle
= INVALID_HANDLE_VALUE
;
1252 int git_path_diriter_init(
1253 git_path_diriter
*diriter
,
1257 assert(diriter
&& path
);
1259 memset(diriter
, 0, sizeof(git_path_diriter
));
1261 if (git_buf_puts(&diriter
->path
, path
) < 0)
1264 git_path_trim_slashes(&diriter
->path
);
1266 if (diriter
->path
.size
== 0) {
1267 giterr_set(GITERR_FILESYSTEM
, "Could not open directory '%s'", path
);
1271 if ((diriter
->dir
= opendir(diriter
->path
.ptr
)) == NULL
) {
1272 git_buf_free(&diriter
->path
);
1274 giterr_set(GITERR_OS
, "Failed to open directory '%s'", path
);
1278 #ifdef GIT_USE_ICONV
1279 if ((flags
& GIT_PATH_DIR_PRECOMPOSE_UNICODE
) != 0)
1280 (void)git_path_iconv_init_precompose(&diriter
->ic
);
1283 diriter
->parent_len
= diriter
->path
.size
;
1284 diriter
->flags
= flags
;
1289 int git_path_diriter_next(git_path_diriter
*diriter
)
1292 const char *filename
;
1293 size_t filename_len
;
1294 bool skip_dot
= !(diriter
->flags
& GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT
);
1302 if ((de
= readdir(diriter
->dir
)) == NULL
) {
1304 return GIT_ITEROVER
;
1306 giterr_set(GITERR_OS
,
1307 "Could not read directory '%s'", diriter
->path
);
1310 } while (skip_dot
&& git_path_is_dot_or_dotdot(de
->d_name
));
1312 filename
= de
->d_name
;
1313 filename_len
= strlen(filename
);
1315 #ifdef GIT_USE_ICONV
1316 if ((diriter
->flags
& GIT_PATH_DIR_PRECOMPOSE_UNICODE
) != 0 &&
1317 (error
= git_path_iconv(&diriter
->ic
, &filename
, &filename_len
)) < 0)
1321 git_buf_truncate(&diriter
->path
, diriter
->parent_len
);
1322 git_buf_putc(&diriter
->path
, '/');
1323 git_buf_put(&diriter
->path
, filename
, filename_len
);
1325 if (git_buf_oom(&diriter
->path
))
1331 int git_path_diriter_filename(
1334 git_path_diriter
*diriter
)
1336 assert(out
&& out_len
&& diriter
);
1338 assert(diriter
->path
.size
> diriter
->parent_len
);
1340 *out
= &diriter
->path
.ptr
[diriter
->parent_len
+1];
1341 *out_len
= diriter
->path
.size
- diriter
->parent_len
- 1;
1345 int git_path_diriter_fullpath(
1348 git_path_diriter
*diriter
)
1350 assert(out
&& out_len
&& diriter
);
1352 *out
= diriter
->path
.ptr
;
1353 *out_len
= diriter
->path
.size
;
1357 int git_path_diriter_stat(struct stat
*out
, git_path_diriter
*diriter
)
1359 assert(out
&& diriter
);
1361 return git_path_lstat(diriter
->path
.ptr
, out
);
1364 void git_path_diriter_free(git_path_diriter
*diriter
)
1366 if (diriter
== NULL
)
1370 closedir(diriter
->dir
);
1371 diriter
->dir
= NULL
;
1374 #ifdef GIT_USE_ICONV
1375 git_path_iconv_clear(&diriter
->ic
);
1378 git_buf_free(&diriter
->path
);
1383 int git_path_dirload(
1384 git_vector
*contents
,
1389 git_path_diriter iter
= GIT_PATH_DIRITER_INIT
;
1395 assert(contents
&& path
);
1397 if ((error
= git_path_diriter_init(&iter
, path
, flags
)) < 0)
1400 while ((error
= git_path_diriter_next(&iter
)) == 0) {
1401 if ((error
= git_path_diriter_fullpath(&name
, &name_len
, &iter
)) < 0)
1404 assert(name_len
> prefix_len
);
1406 dup
= git__strndup(name
+ prefix_len
, name_len
- prefix_len
);
1407 GITERR_CHECK_ALLOC(dup
);
1409 if ((error
= git_vector_insert(contents
, dup
)) < 0)
1413 if (error
== GIT_ITEROVER
)
1416 git_path_diriter_free(&iter
);
1420 int git_path_from_url_or_path(git_buf
*local_path_out
, const char *url_or_path
)
1422 if (git_path_is_local_file_url(url_or_path
))
1423 return git_path_fromurl(local_path_out
, url_or_path
);
1425 return git_buf_sets(local_path_out
, url_or_path
);
1428 /* Reject paths like AUX or COM1, or those versions that end in a dot or
1429 * colon. ("AUX." or "AUX:")
1431 GIT_INLINE(bool) verify_dospath(
1432 const char *component
,
1434 const char dospath
[3],
1437 size_t last
= trailing_num
? 4 : 3;
1439 if (len
< last
|| git__strncasecmp(component
, dospath
, 3) != 0)
1442 if (trailing_num
&& (component
[3] < '1' || component
[3] > '9'))
1445 return (len
> last
&&
1446 component
[last
] != '.' &&
1447 component
[last
] != ':');
1450 static int32_t next_hfs_char(const char **in
, size_t *len
)
1454 int cp_len
= git__utf8_iterate((const uint8_t *)(*in
), (int)(*len
), &codepoint
);
1461 /* these code points are ignored completely */
1462 switch (codepoint
) {
1463 case 0x200c: /* ZERO WIDTH NON-JOINER */
1464 case 0x200d: /* ZERO WIDTH JOINER */
1465 case 0x200e: /* LEFT-TO-RIGHT MARK */
1466 case 0x200f: /* RIGHT-TO-LEFT MARK */
1467 case 0x202a: /* LEFT-TO-RIGHT EMBEDDING */
1468 case 0x202b: /* RIGHT-TO-LEFT EMBEDDING */
1469 case 0x202c: /* POP DIRECTIONAL FORMATTING */
1470 case 0x202d: /* LEFT-TO-RIGHT OVERRIDE */
1471 case 0x202e: /* RIGHT-TO-LEFT OVERRIDE */
1472 case 0x206a: /* INHIBIT SYMMETRIC SWAPPING */
1473 case 0x206b: /* ACTIVATE SYMMETRIC SWAPPING */
1474 case 0x206c: /* INHIBIT ARABIC FORM SHAPING */
1475 case 0x206d: /* ACTIVATE ARABIC FORM SHAPING */
1476 case 0x206e: /* NATIONAL DIGIT SHAPES */
1477 case 0x206f: /* NOMINAL DIGIT SHAPES */
1478 case 0xfeff: /* ZERO WIDTH NO-BREAK SPACE */
1482 /* fold into lowercase -- this will only fold characters in
1483 * the ASCII range, which is perfectly fine, because the
1484 * git folder name can only be composed of ascii characters
1486 return git__tolower(codepoint
);
1488 return 0; /* NULL byte -- end of string */
1491 static bool verify_dotgit_hfs(const char *path
, size_t len
)
1493 if (next_hfs_char(&path
, &len
) != '.' ||
1494 next_hfs_char(&path
, &len
) != 'g' ||
1495 next_hfs_char(&path
, &len
) != 'i' ||
1496 next_hfs_char(&path
, &len
) != 't' ||
1497 next_hfs_char(&path
, &len
) != 0)
1503 GIT_INLINE(bool) verify_dotgit_ntfs(git_repository
*repo
, const char *path
, size_t len
)
1505 git_buf
*reserved
= git_repository__reserved_names_win32
;
1506 size_t reserved_len
= git_repository__reserved_names_win32_len
;
1507 size_t start
= 0, i
;
1510 git_repository__reserved_names(&reserved
, &reserved_len
, repo
, true);
1512 for (i
= 0; i
< reserved_len
; i
++) {
1513 git_buf
*r
= &reserved
[i
];
1515 if (len
>= r
->size
&&
1516 strncasecmp(path
, r
->ptr
, r
->size
) == 0) {
1525 /* Reject paths like ".git\" */
1526 if (path
[start
] == '\\')
1529 /* Reject paths like '.git ' or '.git.' */
1530 for (i
= start
; i
< len
; i
++) {
1531 if (path
[i
] != ' ' && path
[i
] != '.')
1538 GIT_INLINE(bool) verify_char(unsigned char c
, unsigned int flags
)
1540 if ((flags
& GIT_PATH_REJECT_BACKSLASH
) && c
== '\\')
1543 if ((flags
& GIT_PATH_REJECT_SLASH
) && c
== '/')
1546 if (flags
& GIT_PATH_REJECT_NT_CHARS
) {
1566 * We fundamentally don't like some paths when dealing with user-inputted
1567 * strings (in checkout or ref names): we don't want dot or dot-dot
1568 * anywhere, we want to avoid writing weird paths on Windows that can't
1569 * be handled by tools that use the non-\\?\ APIs, we don't want slashes
1570 * or double slashes at the end of paths that can make them ambiguous.
1572 * For checkout, we don't want to recurse into ".git" either.
1574 static bool verify_component(
1575 git_repository
*repo
,
1576 const char *component
,
1583 if ((flags
& GIT_PATH_REJECT_TRAVERSAL
) &&
1584 len
== 1 && component
[0] == '.')
1587 if ((flags
& GIT_PATH_REJECT_TRAVERSAL
) &&
1588 len
== 2 && component
[0] == '.' && component
[1] == '.')
1591 if ((flags
& GIT_PATH_REJECT_TRAILING_DOT
) && component
[len
-1] == '.')
1594 if ((flags
& GIT_PATH_REJECT_TRAILING_SPACE
) && component
[len
-1] == ' ')
1597 if ((flags
& GIT_PATH_REJECT_TRAILING_COLON
) && component
[len
-1] == ':')
1600 if (flags
& GIT_PATH_REJECT_DOS_PATHS
) {
1601 if (!verify_dospath(component
, len
, "CON", false) ||
1602 !verify_dospath(component
, len
, "PRN", false) ||
1603 !verify_dospath(component
, len
, "AUX", false) ||
1604 !verify_dospath(component
, len
, "NUL", false) ||
1605 !verify_dospath(component
, len
, "COM", true) ||
1606 !verify_dospath(component
, len
, "LPT", true))
1610 if (flags
& GIT_PATH_REJECT_DOT_GIT_HFS
&&
1611 !verify_dotgit_hfs(component
, len
))
1614 if (flags
& GIT_PATH_REJECT_DOT_GIT_NTFS
&&
1615 !verify_dotgit_ntfs(repo
, component
, len
))
1618 if ((flags
& GIT_PATH_REJECT_DOT_GIT_HFS
) == 0 &&
1619 (flags
& GIT_PATH_REJECT_DOT_GIT_NTFS
) == 0 &&
1620 (flags
& GIT_PATH_REJECT_DOT_GIT
) &&
1622 component
[0] == '.' &&
1623 (component
[1] == 'g' || component
[1] == 'G') &&
1624 (component
[2] == 'i' || component
[2] == 'I') &&
1625 (component
[3] == 't' || component
[3] == 'T'))
1631 GIT_INLINE(unsigned int) dotgit_flags(
1632 git_repository
*repo
,
1635 int protectHFS
= 0, protectNTFS
= 0;
1645 if (repo
&& !protectHFS
)
1646 git_repository__cvar(&protectHFS
, repo
, GIT_CVAR_PROTECTHFS
);
1648 flags
|= GIT_PATH_REJECT_DOT_GIT_HFS
;
1650 if (repo
&& !protectNTFS
)
1651 git_repository__cvar(&protectNTFS
, repo
, GIT_CVAR_PROTECTNTFS
);
1653 flags
|= GIT_PATH_REJECT_DOT_GIT_NTFS
;
1658 bool git_path_isvalid(
1659 git_repository
*repo
,
1663 const char *start
, *c
;
1665 /* Upgrade the ".git" checks based on platform */
1666 if ((flags
& GIT_PATH_REJECT_DOT_GIT
))
1667 flags
= dotgit_flags(repo
, flags
);
1669 for (start
= c
= path
; *c
; c
++) {
1670 if (!verify_char(*c
, flags
))
1674 if (!verify_component(repo
, start
, (c
- start
), flags
))
1681 return verify_component(repo
, start
, (c
- start
), flags
);
1684 int git_path_normalize_slashes(git_buf
*out
, const char *path
)
1689 if ((error
= git_buf_puts(out
, path
)) < 0)
1692 for (p
= out
->ptr
; *p
; p
++) {