]> git.proxmox.com Git - libgit2.git/blob - src/util/fs_path.c
New upstream version 1.5.0+ds
[libgit2.git] / src / util / fs_path.c
1 /*
2 * Copyright (C) the libgit2 contributors. All rights reserved.
3 *
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.
6 */
7
8 #include "fs_path.h"
9
10 #include "git2_util.h"
11 #include "futils.h"
12 #include "posix.h"
13 #ifdef GIT_WIN32
14 #include "win32/posix.h"
15 #include "win32/w32_buffer.h"
16 #include "win32/w32_util.h"
17 #include "win32/version.h"
18 #include <aclapi.h>
19 #else
20 #include <dirent.h>
21 #endif
22 #include <stdio.h>
23 #include <ctype.h>
24
25 #define ensure_error_set(code) do { \
26 const git_error *e = git_error_last(); \
27 if (!e || !e->message) \
28 git_error_set(e ? e->klass : GIT_ERROR_CALLBACK, \
29 "filesystem callback returned %d", code); \
30 } while(0)
31
32 static int dos_drive_prefix_length(const char *path)
33 {
34 int i;
35
36 /*
37 * Does it start with an ASCII letter (i.e. highest bit not set),
38 * followed by a colon?
39 */
40 if (!(0x80 & (unsigned char)*path))
41 return *path && path[1] == ':' ? 2 : 0;
42
43 /*
44 * While drive letters must be letters of the English alphabet, it is
45 * possible to assign virtually _any_ Unicode character via `subst` as
46 * a drive letter to "virtual drives". Even `1`, or `ä`. Or fun stuff
47 * like this:
48 *
49 * subst ֍: %USERPROFILE%\Desktop
50 */
51 for (i = 1; i < 4 && (0x80 & (unsigned char)path[i]); i++)
52 ; /* skip first UTF-8 character */
53 return path[i] == ':' ? i + 1 : 0;
54 }
55
56 #ifdef GIT_WIN32
57 static bool looks_like_network_computer_name(const char *path, int pos)
58 {
59 if (pos < 3)
60 return false;
61
62 if (path[0] != '/' || path[1] != '/')
63 return false;
64
65 while (pos-- > 2) {
66 if (path[pos] == '/')
67 return false;
68 }
69
70 return true;
71 }
72 #endif
73
74 /*
75 * Based on the Android implementation, BSD licensed.
76 * http://android.git.kernel.org/
77 *
78 * Copyright (C) 2008 The Android Open Source Project
79 * All rights reserved.
80 *
81 * Redistribution and use in source and binary forms, with or without
82 * modification, are permitted provided that the following conditions
83 * are met:
84 * * Redistributions of source code must retain the above copyright
85 * notice, this list of conditions and the following disclaimer.
86 * * Redistributions in binary form must reproduce the above copyright
87 * notice, this list of conditions and the following disclaimer in
88 * the documentation and/or other materials provided with the
89 * distribution.
90 *
91 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
92 * AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
93 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
94 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
95 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
96 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
97 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
98 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
99 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
100 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
101 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
102 * SUCH DAMAGE.
103 */
104 int git_fs_path_basename_r(git_str *buffer, const char *path)
105 {
106 const char *endp, *startp;
107 int len, result;
108
109 /* Empty or NULL string gets treated as "." */
110 if (path == NULL || *path == '\0') {
111 startp = ".";
112 len = 1;
113 goto Exit;
114 }
115
116 /* Strip trailing slashes */
117 endp = path + strlen(path) - 1;
118 while (endp > path && *endp == '/')
119 endp--;
120
121 /* All slashes becomes "/" */
122 if (endp == path && *endp == '/') {
123 startp = "/";
124 len = 1;
125 goto Exit;
126 }
127
128 /* Find the start of the base */
129 startp = endp;
130 while (startp > path && *(startp - 1) != '/')
131 startp--;
132
133 /* Cast is safe because max path < max int */
134 len = (int)(endp - startp + 1);
135
136 Exit:
137 result = len;
138
139 if (buffer != NULL && git_str_set(buffer, startp, len) < 0)
140 return -1;
141
142 return result;
143 }
144
145 /*
146 * Determine if the path is a Windows prefix and, if so, returns
147 * its actual length. If it is not a prefix, returns -1.
148 */
149 static int win32_prefix_length(const char *path, int len)
150 {
151 #ifndef GIT_WIN32
152 GIT_UNUSED(path);
153 GIT_UNUSED(len);
154 #else
155 /*
156 * Mimic unix behavior where '/.git' returns '/': 'C:/.git'
157 * will return 'C:/' here
158 */
159 if (dos_drive_prefix_length(path) == len)
160 return len;
161
162 /*
163 * Similarly checks if we're dealing with a network computer name
164 * '//computername/.git' will return '//computername/'
165 */
166 if (looks_like_network_computer_name(path, len))
167 return len;
168 #endif
169
170 return -1;
171 }
172
173 /*
174 * Based on the Android implementation, BSD licensed.
175 * Check http://android.git.kernel.org/
176 */
177 int git_fs_path_dirname_r(git_str *buffer, const char *path)
178 {
179 const char *endp;
180 int is_prefix = 0, len;
181
182 /* Empty or NULL string gets treated as "." */
183 if (path == NULL || *path == '\0') {
184 path = ".";
185 len = 1;
186 goto Exit;
187 }
188
189 /* Strip trailing slashes */
190 endp = path + strlen(path) - 1;
191 while (endp > path && *endp == '/')
192 endp--;
193
194 if (endp - path + 1 > INT_MAX) {
195 git_error_set(GIT_ERROR_INVALID, "path too long");
196 return -1;
197 }
198
199 if ((len = win32_prefix_length(path, (int)(endp - path + 1))) > 0) {
200 is_prefix = 1;
201 goto Exit;
202 }
203
204 /* Find the start of the dir */
205 while (endp > path && *endp != '/')
206 endp--;
207
208 /* Either the dir is "/" or there are no slashes */
209 if (endp == path) {
210 path = (*endp == '/') ? "/" : ".";
211 len = 1;
212 goto Exit;
213 }
214
215 do {
216 endp--;
217 } while (endp > path && *endp == '/');
218
219 if (endp - path + 1 > INT_MAX) {
220 git_error_set(GIT_ERROR_INVALID, "path too long");
221 return -1;
222 }
223
224 if ((len = win32_prefix_length(path, (int)(endp - path + 1))) > 0) {
225 is_prefix = 1;
226 goto Exit;
227 }
228
229 /* Cast is safe because max path < max int */
230 len = (int)(endp - path + 1);
231
232 Exit:
233 if (buffer) {
234 if (git_str_set(buffer, path, len) < 0)
235 return -1;
236 if (is_prefix && git_str_putc(buffer, '/') < 0)
237 return -1;
238 }
239
240 return len;
241 }
242
243
244 char *git_fs_path_dirname(const char *path)
245 {
246 git_str buf = GIT_STR_INIT;
247 char *dirname;
248
249 git_fs_path_dirname_r(&buf, path);
250 dirname = git_str_detach(&buf);
251 git_str_dispose(&buf); /* avoid memleak if error occurs */
252
253 return dirname;
254 }
255
256 char *git_fs_path_basename(const char *path)
257 {
258 git_str buf = GIT_STR_INIT;
259 char *basename;
260
261 git_fs_path_basename_r(&buf, path);
262 basename = git_str_detach(&buf);
263 git_str_dispose(&buf); /* avoid memleak if error occurs */
264
265 return basename;
266 }
267
268 size_t git_fs_path_basename_offset(git_str *buffer)
269 {
270 ssize_t slash;
271
272 if (!buffer || buffer->size <= 0)
273 return 0;
274
275 slash = git_str_rfind_next(buffer, '/');
276
277 if (slash >= 0 && buffer->ptr[slash] == '/')
278 return (size_t)(slash + 1);
279
280 return 0;
281 }
282
283 int git_fs_path_root(const char *path)
284 {
285 int offset = 0, prefix_len;
286
287 /* Does the root of the path look like a windows drive ? */
288 if ((prefix_len = dos_drive_prefix_length(path)))
289 offset += prefix_len;
290
291 #ifdef GIT_WIN32
292 /* Are we dealing with a windows network path? */
293 else if ((path[0] == '/' && path[1] == '/' && path[2] != '/') ||
294 (path[0] == '\\' && path[1] == '\\' && path[2] != '\\'))
295 {
296 offset += 2;
297
298 /* Skip the computer name segment */
299 while (path[offset] && path[offset] != '/' && path[offset] != '\\')
300 offset++;
301 }
302
303 if (path[offset] == '\\')
304 return offset;
305 #endif
306
307 if (path[offset] == '/')
308 return offset;
309
310 return -1; /* Not a real error - signals that path is not rooted */
311 }
312
313 static void path_trim_slashes(git_str *path)
314 {
315 int ceiling = git_fs_path_root(path->ptr) + 1;
316
317 if (ceiling < 0)
318 return;
319
320 while (path->size > (size_t)ceiling) {
321 if (path->ptr[path->size-1] != '/')
322 break;
323
324 path->ptr[path->size-1] = '\0';
325 path->size--;
326 }
327 }
328
329 int git_fs_path_join_unrooted(
330 git_str *path_out, const char *path, const char *base, ssize_t *root_at)
331 {
332 ssize_t root;
333
334 GIT_ASSERT_ARG(path_out);
335 GIT_ASSERT_ARG(path);
336
337 root = (ssize_t)git_fs_path_root(path);
338
339 if (base != NULL && root < 0) {
340 if (git_str_joinpath(path_out, base, path) < 0)
341 return -1;
342
343 root = (ssize_t)strlen(base);
344 } else {
345 if (git_str_sets(path_out, path) < 0)
346 return -1;
347
348 if (root < 0)
349 root = 0;
350 else if (base)
351 git_fs_path_equal_or_prefixed(base, path, &root);
352 }
353
354 if (root_at)
355 *root_at = root;
356
357 return 0;
358 }
359
360 void git_fs_path_squash_slashes(git_str *path)
361 {
362 char *p, *q;
363
364 if (path->size == 0)
365 return;
366
367 for (p = path->ptr, q = path->ptr; *q; p++, q++) {
368 *p = *q;
369
370 while (*q == '/' && *(q+1) == '/') {
371 path->size--;
372 q++;
373 }
374 }
375
376 *p = '\0';
377 }
378
379 int git_fs_path_prettify(git_str *path_out, const char *path, const char *base)
380 {
381 char buf[GIT_PATH_MAX];
382
383 GIT_ASSERT_ARG(path_out);
384 GIT_ASSERT_ARG(path);
385
386 /* construct path if needed */
387 if (base != NULL && git_fs_path_root(path) < 0) {
388 if (git_str_joinpath(path_out, base, path) < 0)
389 return -1;
390 path = path_out->ptr;
391 }
392
393 if (p_realpath(path, buf) == NULL) {
394 /* git_error_set resets the errno when dealing with a GIT_ERROR_OS kind of error */
395 int error = (errno == ENOENT || errno == ENOTDIR) ? GIT_ENOTFOUND : -1;
396 git_error_set(GIT_ERROR_OS, "failed to resolve path '%s'", path);
397
398 git_str_clear(path_out);
399
400 return error;
401 }
402
403 return git_str_sets(path_out, buf);
404 }
405
406 int git_fs_path_prettify_dir(git_str *path_out, const char *path, const char *base)
407 {
408 int error = git_fs_path_prettify(path_out, path, base);
409 return (error < 0) ? error : git_fs_path_to_dir(path_out);
410 }
411
412 int git_fs_path_to_dir(git_str *path)
413 {
414 if (path->asize > 0 &&
415 git_str_len(path) > 0 &&
416 path->ptr[git_str_len(path) - 1] != '/')
417 git_str_putc(path, '/');
418
419 return git_str_oom(path) ? -1 : 0;
420 }
421
422 void git_fs_path_string_to_dir(char *path, size_t size)
423 {
424 size_t end = strlen(path);
425
426 if (end && path[end - 1] != '/' && end < size) {
427 path[end] = '/';
428 path[end + 1] = '\0';
429 }
430 }
431
432 int git__percent_decode(git_str *decoded_out, const char *input)
433 {
434 int len, hi, lo, i;
435
436 GIT_ASSERT_ARG(decoded_out);
437 GIT_ASSERT_ARG(input);
438
439 len = (int)strlen(input);
440 git_str_clear(decoded_out);
441
442 for(i = 0; i < len; i++)
443 {
444 char c = input[i];
445
446 if (c != '%')
447 goto append;
448
449 if (i >= len - 2)
450 goto append;
451
452 hi = git__fromhex(input[i + 1]);
453 lo = git__fromhex(input[i + 2]);
454
455 if (hi < 0 || lo < 0)
456 goto append;
457
458 c = (char)(hi << 4 | lo);
459 i += 2;
460
461 append:
462 if (git_str_putc(decoded_out, c) < 0)
463 return -1;
464 }
465
466 return 0;
467 }
468
469 static int error_invalid_local_file_uri(const char *uri)
470 {
471 git_error_set(GIT_ERROR_CONFIG, "'%s' is not a valid local file URI", uri);
472 return -1;
473 }
474
475 static int local_file_url_prefixlen(const char *file_url)
476 {
477 int len = -1;
478
479 if (git__prefixcmp(file_url, "file://") == 0) {
480 if (file_url[7] == '/')
481 len = 8;
482 else if (git__prefixcmp(file_url + 7, "localhost/") == 0)
483 len = 17;
484 }
485
486 return len;
487 }
488
489 bool git_fs_path_is_local_file_url(const char *file_url)
490 {
491 return (local_file_url_prefixlen(file_url) > 0);
492 }
493
494 int git_fs_path_fromurl(git_str *local_path_out, const char *file_url)
495 {
496 int offset;
497
498 GIT_ASSERT_ARG(local_path_out);
499 GIT_ASSERT_ARG(file_url);
500
501 if ((offset = local_file_url_prefixlen(file_url)) < 0 ||
502 file_url[offset] == '\0' || file_url[offset] == '/')
503 return error_invalid_local_file_uri(file_url);
504
505 #ifndef GIT_WIN32
506 offset--; /* A *nix absolute path starts with a forward slash */
507 #endif
508
509 git_str_clear(local_path_out);
510 return git__percent_decode(local_path_out, file_url + offset);
511 }
512
513 int git_fs_path_walk_up(
514 git_str *path,
515 const char *ceiling,
516 int (*cb)(void *data, const char *),
517 void *data)
518 {
519 int error = 0;
520 git_str iter;
521 ssize_t stop = 0, scan;
522 char oldc = '\0';
523
524 GIT_ASSERT_ARG(path);
525 GIT_ASSERT_ARG(cb);
526
527 if (ceiling != NULL) {
528 if (git__prefixcmp(path->ptr, ceiling) == 0)
529 stop = (ssize_t)strlen(ceiling);
530 else
531 stop = git_str_len(path);
532 }
533 scan = git_str_len(path);
534
535 /* empty path: yield only once */
536 if (!scan) {
537 error = cb(data, "");
538 if (error)
539 ensure_error_set(error);
540 return error;
541 }
542
543 iter.ptr = path->ptr;
544 iter.size = git_str_len(path);
545 iter.asize = path->asize;
546
547 while (scan >= stop) {
548 error = cb(data, iter.ptr);
549 iter.ptr[scan] = oldc;
550
551 if (error) {
552 ensure_error_set(error);
553 break;
554 }
555
556 scan = git_str_rfind_next(&iter, '/');
557 if (scan >= 0) {
558 scan++;
559 oldc = iter.ptr[scan];
560 iter.size = scan;
561 iter.ptr[scan] = '\0';
562 }
563 }
564
565 if (scan >= 0)
566 iter.ptr[scan] = oldc;
567
568 /* relative path: yield for the last component */
569 if (!error && stop == 0 && iter.ptr[0] != '/') {
570 error = cb(data, "");
571 if (error)
572 ensure_error_set(error);
573 }
574
575 return error;
576 }
577
578 bool git_fs_path_exists(const char *path)
579 {
580 GIT_ASSERT_ARG_WITH_RETVAL(path, false);
581 return p_access(path, F_OK) == 0;
582 }
583
584 bool git_fs_path_isdir(const char *path)
585 {
586 struct stat st;
587 if (p_stat(path, &st) < 0)
588 return false;
589
590 return S_ISDIR(st.st_mode) != 0;
591 }
592
593 bool git_fs_path_isfile(const char *path)
594 {
595 struct stat st;
596
597 GIT_ASSERT_ARG_WITH_RETVAL(path, false);
598 if (p_stat(path, &st) < 0)
599 return false;
600
601 return S_ISREG(st.st_mode) != 0;
602 }
603
604 bool git_fs_path_islink(const char *path)
605 {
606 struct stat st;
607
608 GIT_ASSERT_ARG_WITH_RETVAL(path, false);
609 if (p_lstat(path, &st) < 0)
610 return false;
611
612 return S_ISLNK(st.st_mode) != 0;
613 }
614
615 #ifdef GIT_WIN32
616
617 bool git_fs_path_is_empty_dir(const char *path)
618 {
619 git_win32_path filter_w;
620 bool empty = false;
621
622 if (git_win32__findfirstfile_filter(filter_w, path)) {
623 WIN32_FIND_DATAW findData;
624 HANDLE hFind = FindFirstFileW(filter_w, &findData);
625
626 /* FindFirstFile will fail if there are no children to the given
627 * path, which can happen if the given path is a file (and obviously
628 * has no children) or if the given path is an empty mount point.
629 * (Most directories have at least directory entries '.' and '..',
630 * but ridiculously another volume mounted in another drive letter's
631 * path space do not, and thus have nothing to enumerate.) If
632 * FindFirstFile fails, check if this is a directory-like thing
633 * (a mount point).
634 */
635 if (hFind == INVALID_HANDLE_VALUE)
636 return git_fs_path_isdir(path);
637
638 /* If the find handle was created successfully, then it's a directory */
639 empty = true;
640
641 do {
642 /* Allow the enumeration to return . and .. and still be considered
643 * empty. In the special case of drive roots (i.e. C:\) where . and
644 * .. do not occur, we can still consider the path to be an empty
645 * directory if there's nothing there. */
646 if (!git_fs_path_is_dot_or_dotdotW(findData.cFileName)) {
647 empty = false;
648 break;
649 }
650 } while (FindNextFileW(hFind, &findData));
651
652 FindClose(hFind);
653 }
654
655 return empty;
656 }
657
658 #else
659
660 static int path_found_entry(void *payload, git_str *path)
661 {
662 GIT_UNUSED(payload);
663 return !git_fs_path_is_dot_or_dotdot(path->ptr);
664 }
665
666 bool git_fs_path_is_empty_dir(const char *path)
667 {
668 int error;
669 git_str dir = GIT_STR_INIT;
670
671 if (!git_fs_path_isdir(path))
672 return false;
673
674 if ((error = git_str_sets(&dir, path)) != 0)
675 git_error_clear();
676 else
677 error = git_fs_path_direach(&dir, 0, path_found_entry, NULL);
678
679 git_str_dispose(&dir);
680
681 return !error;
682 }
683
684 #endif
685
686 int git_fs_path_set_error(int errno_value, const char *path, const char *action)
687 {
688 switch (errno_value) {
689 case ENOENT:
690 case ENOTDIR:
691 git_error_set(GIT_ERROR_OS, "could not find '%s' to %s", path, action);
692 return GIT_ENOTFOUND;
693
694 case EINVAL:
695 case ENAMETOOLONG:
696 git_error_set(GIT_ERROR_OS, "invalid path for filesystem '%s'", path);
697 return GIT_EINVALIDSPEC;
698
699 case EEXIST:
700 git_error_set(GIT_ERROR_OS, "failed %s - '%s' already exists", action, path);
701 return GIT_EEXISTS;
702
703 case EACCES:
704 git_error_set(GIT_ERROR_OS, "failed %s - '%s' is locked", action, path);
705 return GIT_ELOCKED;
706
707 default:
708 git_error_set(GIT_ERROR_OS, "could not %s '%s'", action, path);
709 return -1;
710 }
711 }
712
713 int git_fs_path_lstat(const char *path, struct stat *st)
714 {
715 if (p_lstat(path, st) == 0)
716 return 0;
717
718 return git_fs_path_set_error(errno, path, "stat");
719 }
720
721 static bool _check_dir_contents(
722 git_str *dir,
723 const char *sub,
724 bool (*predicate)(const char *))
725 {
726 bool result;
727 size_t dir_size = git_str_len(dir);
728 size_t sub_size = strlen(sub);
729 size_t alloc_size;
730
731 /* leave base valid even if we could not make space for subdir */
732 if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, dir_size, sub_size) ||
733 GIT_ADD_SIZET_OVERFLOW(&alloc_size, alloc_size, 2) ||
734 git_str_try_grow(dir, alloc_size, false) < 0)
735 return false;
736
737 /* save excursion */
738 if (git_str_joinpath(dir, dir->ptr, sub) < 0)
739 return false;
740
741 result = predicate(dir->ptr);
742
743 /* restore path */
744 git_str_truncate(dir, dir_size);
745 return result;
746 }
747
748 bool git_fs_path_contains(git_str *dir, const char *item)
749 {
750 return _check_dir_contents(dir, item, &git_fs_path_exists);
751 }
752
753 bool git_fs_path_contains_dir(git_str *base, const char *subdir)
754 {
755 return _check_dir_contents(base, subdir, &git_fs_path_isdir);
756 }
757
758 bool git_fs_path_contains_file(git_str *base, const char *file)
759 {
760 return _check_dir_contents(base, file, &git_fs_path_isfile);
761 }
762
763 int git_fs_path_find_dir(git_str *dir)
764 {
765 int error = 0;
766 char buf[GIT_PATH_MAX];
767
768 if (p_realpath(dir->ptr, buf) != NULL)
769 error = git_str_sets(dir, buf);
770
771 /* call dirname if this is not a directory */
772 if (!error) /* && git_fs_path_isdir(dir->ptr) == false) */
773 error = (git_fs_path_dirname_r(dir, dir->ptr) < 0) ? -1 : 0;
774
775 if (!error)
776 error = git_fs_path_to_dir(dir);
777
778 return error;
779 }
780
781 int git_fs_path_resolve_relative(git_str *path, size_t ceiling)
782 {
783 char *base, *to, *from, *next;
784 size_t len;
785
786 GIT_ERROR_CHECK_ALLOC_STR(path);
787
788 if (ceiling > path->size)
789 ceiling = path->size;
790
791 /* recognize drive prefixes, etc. that should not be backed over */
792 if (ceiling == 0)
793 ceiling = git_fs_path_root(path->ptr) + 1;
794
795 /* recognize URL prefixes that should not be backed over */
796 if (ceiling == 0) {
797 for (next = path->ptr; *next && git__isalpha(*next); ++next);
798 if (next[0] == ':' && next[1] == '/' && next[2] == '/')
799 ceiling = (next + 3) - path->ptr;
800 }
801
802 base = to = from = path->ptr + ceiling;
803
804 while (*from) {
805 for (next = from; *next && *next != '/'; ++next);
806
807 len = next - from;
808
809 if (len == 1 && from[0] == '.')
810 /* do nothing with singleton dot */;
811
812 else if (len == 2 && from[0] == '.' && from[1] == '.') {
813 /* error out if trying to up one from a hard base */
814 if (to == base && ceiling != 0) {
815 git_error_set(GIT_ERROR_INVALID,
816 "cannot strip root component off url");
817 return -1;
818 }
819
820 /* no more path segments to strip,
821 * use '../' as a new base path */
822 if (to == base) {
823 if (*next == '/')
824 len++;
825
826 if (to != from)
827 memmove(to, from, len);
828
829 to += len;
830 /* this is now the base, can't back up from a
831 * relative prefix */
832 base = to;
833 } else {
834 /* back up a path segment */
835 while (to > base && to[-1] == '/') to--;
836 while (to > base && to[-1] != '/') to--;
837 }
838 } else {
839 if (*next == '/' && *from != '/')
840 len++;
841
842 if (to != from)
843 memmove(to, from, len);
844
845 to += len;
846 }
847
848 from += len;
849
850 while (*from == '/') from++;
851 }
852
853 *to = '\0';
854
855 path->size = to - path->ptr;
856
857 return 0;
858 }
859
860 int git_fs_path_apply_relative(git_str *target, const char *relpath)
861 {
862 return git_str_joinpath(target, git_str_cstr(target), relpath) ||
863 git_fs_path_resolve_relative(target, 0);
864 }
865
866 int git_fs_path_cmp(
867 const char *name1, size_t len1, int isdir1,
868 const char *name2, size_t len2, int isdir2,
869 int (*compare)(const char *, const char *, size_t))
870 {
871 unsigned char c1, c2;
872 size_t len = len1 < len2 ? len1 : len2;
873 int cmp;
874
875 cmp = compare(name1, name2, len);
876 if (cmp)
877 return cmp;
878
879 c1 = name1[len];
880 c2 = name2[len];
881
882 if (c1 == '\0' && isdir1)
883 c1 = '/';
884
885 if (c2 == '\0' && isdir2)
886 c2 = '/';
887
888 return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
889 }
890
891 size_t git_fs_path_common_dirlen(const char *one, const char *two)
892 {
893 const char *p, *q, *dirsep = NULL;
894
895 for (p = one, q = two; *p && *q; p++, q++) {
896 if (*p == '/' && *q == '/')
897 dirsep = p;
898 else if (*p != *q)
899 break;
900 }
901
902 return dirsep ? (dirsep - one) + 1 : 0;
903 }
904
905 int git_fs_path_make_relative(git_str *path, const char *parent)
906 {
907 const char *p, *q, *p_dirsep, *q_dirsep;
908 size_t plen = path->size, newlen, alloclen, depth = 1, i, offset;
909
910 for (p_dirsep = p = path->ptr, q_dirsep = q = parent; *p && *q; p++, q++) {
911 if (*p == '/' && *q == '/') {
912 p_dirsep = p;
913 q_dirsep = q;
914 }
915 else if (*p != *q)
916 break;
917 }
918
919 /* need at least 1 common path segment */
920 if ((p_dirsep == path->ptr || q_dirsep == parent) &&
921 (*p_dirsep != '/' || *q_dirsep != '/')) {
922 git_error_set(GIT_ERROR_INVALID,
923 "%s is not a parent of %s", parent, path->ptr);
924 return GIT_ENOTFOUND;
925 }
926
927 if (*p == '/' && !*q)
928 p++;
929 else if (!*p && *q == '/')
930 q++;
931 else if (!*p && !*q)
932 return git_str_clear(path), 0;
933 else {
934 p = p_dirsep + 1;
935 q = q_dirsep + 1;
936 }
937
938 plen -= (p - path->ptr);
939
940 if (!*q)
941 return git_str_set(path, p, plen);
942
943 for (; (q = strchr(q, '/')) && *(q + 1); q++)
944 depth++;
945
946 GIT_ERROR_CHECK_ALLOC_MULTIPLY(&newlen, depth, 3);
947 GIT_ERROR_CHECK_ALLOC_ADD(&newlen, newlen, plen);
948
949 GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, newlen, 1);
950
951 /* save the offset as we might realllocate the pointer */
952 offset = p - path->ptr;
953 if (git_str_try_grow(path, alloclen, 1) < 0)
954 return -1;
955 p = path->ptr + offset;
956
957 memmove(path->ptr + (depth * 3), p, plen + 1);
958
959 for (i = 0; i < depth; i++)
960 memcpy(path->ptr + (i * 3), "../", 3);
961
962 path->size = newlen;
963 return 0;
964 }
965
966 bool git_fs_path_has_non_ascii(const char *path, size_t pathlen)
967 {
968 const uint8_t *scan = (const uint8_t *)path, *end;
969
970 for (end = scan + pathlen; scan < end; ++scan)
971 if (*scan & 0x80)
972 return true;
973
974 return false;
975 }
976
977 #ifdef GIT_USE_ICONV
978
979 int git_fs_path_iconv_init_precompose(git_fs_path_iconv_t *ic)
980 {
981 git_str_init(&ic->buf, 0);
982 ic->map = iconv_open(GIT_PATH_REPO_ENCODING, GIT_PATH_NATIVE_ENCODING);
983 return 0;
984 }
985
986 void git_fs_path_iconv_clear(git_fs_path_iconv_t *ic)
987 {
988 if (ic) {
989 if (ic->map != (iconv_t)-1)
990 iconv_close(ic->map);
991 git_str_dispose(&ic->buf);
992 }
993 }
994
995 int git_fs_path_iconv(git_fs_path_iconv_t *ic, const char **in, size_t *inlen)
996 {
997 char *nfd = (char*)*in, *nfc;
998 size_t nfdlen = *inlen, nfclen, wantlen = nfdlen, alloclen, rv;
999 int retry = 1;
1000
1001 if (!ic || ic->map == (iconv_t)-1 ||
1002 !git_fs_path_has_non_ascii(*in, *inlen))
1003 return 0;
1004
1005 git_str_clear(&ic->buf);
1006
1007 while (1) {
1008 GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, wantlen, 1);
1009 if (git_str_grow(&ic->buf, alloclen) < 0)
1010 return -1;
1011
1012 nfc = ic->buf.ptr + ic->buf.size;
1013 nfclen = ic->buf.asize - ic->buf.size;
1014
1015 rv = iconv(ic->map, &nfd, &nfdlen, &nfc, &nfclen);
1016
1017 ic->buf.size = (nfc - ic->buf.ptr);
1018
1019 if (rv != (size_t)-1)
1020 break;
1021
1022 /* if we cannot convert the data (probably because iconv thinks
1023 * it is not valid UTF-8 source data), then use original data
1024 */
1025 if (errno != E2BIG)
1026 return 0;
1027
1028 /* make space for 2x the remaining data to be converted
1029 * (with per retry overhead to avoid infinite loops)
1030 */
1031 wantlen = ic->buf.size + max(nfclen, nfdlen) * 2 + (size_t)(retry * 4);
1032
1033 if (retry++ > 4)
1034 goto fail;
1035 }
1036
1037 ic->buf.ptr[ic->buf.size] = '\0';
1038
1039 *in = ic->buf.ptr;
1040 *inlen = ic->buf.size;
1041
1042 return 0;
1043
1044 fail:
1045 git_error_set(GIT_ERROR_OS, "unable to convert unicode path data");
1046 return -1;
1047 }
1048
1049 static const char *nfc_file = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D";
1050 static const char *nfd_file = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D";
1051
1052 /* Check if the platform is decomposing unicode data for us. We will
1053 * emulate core Git and prefer to use precomposed unicode data internally
1054 * on these platforms, composing the decomposed unicode on the fly.
1055 *
1056 * This mainly happens on the Mac where HDFS stores filenames as
1057 * decomposed unicode. Even on VFAT and SAMBA file systems, the Mac will
1058 * return decomposed unicode from readdir() even when the actual
1059 * filesystem is storing precomposed unicode.
1060 */
1061 bool git_fs_path_does_decompose_unicode(const char *root)
1062 {
1063 git_str nfc_path = GIT_STR_INIT;
1064 git_str nfd_path = GIT_STR_INIT;
1065 int fd;
1066 bool found_decomposed = false;
1067 size_t orig_len;
1068 const char *trailer;
1069
1070 /* Create a file using a precomposed path and then try to find it
1071 * using the decomposed name. If the lookup fails, then we will mark
1072 * that we should precompose unicode for this repository.
1073 */
1074 if (git_str_joinpath(&nfc_path, root, nfc_file) < 0)
1075 goto done;
1076
1077 /* record original path length before trailer */
1078 orig_len = nfc_path.size;
1079
1080 if ((fd = git_futils_mktmp(&nfc_path, nfc_path.ptr, 0666)) < 0)
1081 goto done;
1082 p_close(fd);
1083
1084 trailer = nfc_path.ptr + orig_len;
1085
1086 /* try to look up as NFD path */
1087 if (git_str_joinpath(&nfd_path, root, nfd_file) < 0 ||
1088 git_str_puts(&nfd_path, trailer) < 0)
1089 goto done;
1090
1091 found_decomposed = git_fs_path_exists(nfd_path.ptr);
1092
1093 /* remove temporary file (using original precomposed path) */
1094 (void)p_unlink(nfc_path.ptr);
1095
1096 done:
1097 git_str_dispose(&nfc_path);
1098 git_str_dispose(&nfd_path);
1099 return found_decomposed;
1100 }
1101
1102 #else
1103
1104 bool git_fs_path_does_decompose_unicode(const char *root)
1105 {
1106 GIT_UNUSED(root);
1107 return false;
1108 }
1109
1110 #endif
1111
1112 #if defined(__sun) || defined(__GNU__)
1113 typedef char path_dirent_data[sizeof(struct dirent) + FILENAME_MAX + 1];
1114 #else
1115 typedef struct dirent path_dirent_data;
1116 #endif
1117
1118 int git_fs_path_direach(
1119 git_str *path,
1120 uint32_t flags,
1121 int (*fn)(void *, git_str *),
1122 void *arg)
1123 {
1124 int error = 0;
1125 ssize_t wd_len;
1126 DIR *dir;
1127 struct dirent *de;
1128
1129 #ifdef GIT_USE_ICONV
1130 git_fs_path_iconv_t ic = GIT_PATH_ICONV_INIT;
1131 #endif
1132
1133 GIT_UNUSED(flags);
1134
1135 if (git_fs_path_to_dir(path) < 0)
1136 return -1;
1137
1138 wd_len = git_str_len(path);
1139
1140 if ((dir = opendir(path->ptr)) == NULL) {
1141 git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path->ptr);
1142 if (errno == ENOENT)
1143 return GIT_ENOTFOUND;
1144
1145 return -1;
1146 }
1147
1148 #ifdef GIT_USE_ICONV
1149 if ((flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0)
1150 (void)git_fs_path_iconv_init_precompose(&ic);
1151 #endif
1152
1153 while ((de = readdir(dir)) != NULL) {
1154 const char *de_path = de->d_name;
1155 size_t de_len = strlen(de_path);
1156
1157 if (git_fs_path_is_dot_or_dotdot(de_path))
1158 continue;
1159
1160 #ifdef GIT_USE_ICONV
1161 if ((error = git_fs_path_iconv(&ic, &de_path, &de_len)) < 0)
1162 break;
1163 #endif
1164
1165 if ((error = git_str_put(path, de_path, de_len)) < 0)
1166 break;
1167
1168 git_error_clear();
1169 error = fn(arg, path);
1170
1171 git_str_truncate(path, wd_len); /* restore path */
1172
1173 /* Only set our own error if the callback did not set one already */
1174 if (error != 0) {
1175 if (!git_error_last())
1176 ensure_error_set(error);
1177
1178 break;
1179 }
1180 }
1181
1182 closedir(dir);
1183
1184 #ifdef GIT_USE_ICONV
1185 git_fs_path_iconv_clear(&ic);
1186 #endif
1187
1188 return error;
1189 }
1190
1191 #if defined(GIT_WIN32) && !defined(__MINGW32__)
1192
1193 /* Using _FIND_FIRST_EX_LARGE_FETCH may increase performance in Windows 7
1194 * and better.
1195 */
1196 #ifndef FIND_FIRST_EX_LARGE_FETCH
1197 # define FIND_FIRST_EX_LARGE_FETCH 2
1198 #endif
1199
1200 int git_fs_path_diriter_init(
1201 git_fs_path_diriter *diriter,
1202 const char *path,
1203 unsigned int flags)
1204 {
1205 git_win32_path path_filter;
1206
1207 static int is_win7_or_later = -1;
1208 if (is_win7_or_later < 0)
1209 is_win7_or_later = git_has_win32_version(6, 1, 0);
1210
1211 GIT_ASSERT_ARG(diriter);
1212 GIT_ASSERT_ARG(path);
1213
1214 memset(diriter, 0, sizeof(git_fs_path_diriter));
1215 diriter->handle = INVALID_HANDLE_VALUE;
1216
1217 if (git_str_puts(&diriter->path_utf8, path) < 0)
1218 return -1;
1219
1220 path_trim_slashes(&diriter->path_utf8);
1221
1222 if (diriter->path_utf8.size == 0) {
1223 git_error_set(GIT_ERROR_FILESYSTEM, "could not open directory '%s'", path);
1224 return -1;
1225 }
1226
1227 if ((diriter->parent_len = git_win32_path_from_utf8(diriter->path, diriter->path_utf8.ptr)) < 0 ||
1228 !git_win32__findfirstfile_filter(path_filter, diriter->path_utf8.ptr)) {
1229 git_error_set(GIT_ERROR_OS, "could not parse the directory path '%s'", path);
1230 return -1;
1231 }
1232
1233 diriter->handle = FindFirstFileExW(
1234 path_filter,
1235 is_win7_or_later ? FindExInfoBasic : FindExInfoStandard,
1236 &diriter->current,
1237 FindExSearchNameMatch,
1238 NULL,
1239 is_win7_or_later ? FIND_FIRST_EX_LARGE_FETCH : 0);
1240
1241 if (diriter->handle == INVALID_HANDLE_VALUE) {
1242 git_error_set(GIT_ERROR_OS, "could not open directory '%s'", path);
1243 return -1;
1244 }
1245
1246 diriter->parent_utf8_len = diriter->path_utf8.size;
1247 diriter->flags = flags;
1248 return 0;
1249 }
1250
1251 static int diriter_update_paths(git_fs_path_diriter *diriter)
1252 {
1253 size_t filename_len, path_len;
1254
1255 filename_len = wcslen(diriter->current.cFileName);
1256
1257 if (GIT_ADD_SIZET_OVERFLOW(&path_len, diriter->parent_len, filename_len) ||
1258 GIT_ADD_SIZET_OVERFLOW(&path_len, path_len, 2))
1259 return -1;
1260
1261 if (path_len > GIT_WIN_PATH_UTF16) {
1262 git_error_set(GIT_ERROR_FILESYSTEM,
1263 "invalid path '%.*ls\\%ls' (path too long)",
1264 diriter->parent_len, diriter->path, diriter->current.cFileName);
1265 return -1;
1266 }
1267
1268 diriter->path[diriter->parent_len] = L'\\';
1269 memcpy(&diriter->path[diriter->parent_len+1],
1270 diriter->current.cFileName, filename_len * sizeof(wchar_t));
1271 diriter->path[path_len-1] = L'\0';
1272
1273 git_str_truncate(&diriter->path_utf8, diriter->parent_utf8_len);
1274
1275 if (diriter->parent_utf8_len > 0 &&
1276 diriter->path_utf8.ptr[diriter->parent_utf8_len-1] != '/')
1277 git_str_putc(&diriter->path_utf8, '/');
1278
1279 git_str_put_w(&diriter->path_utf8, diriter->current.cFileName, filename_len);
1280
1281 if (git_str_oom(&diriter->path_utf8))
1282 return -1;
1283
1284 return 0;
1285 }
1286
1287 int git_fs_path_diriter_next(git_fs_path_diriter *diriter)
1288 {
1289 bool skip_dot = !(diriter->flags & GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT);
1290
1291 do {
1292 /* Our first time through, we already have the data from
1293 * FindFirstFileW. Use it, otherwise get the next file.
1294 */
1295 if (!diriter->needs_next)
1296 diriter->needs_next = 1;
1297 else if (!FindNextFileW(diriter->handle, &diriter->current))
1298 return GIT_ITEROVER;
1299 } while (skip_dot && git_fs_path_is_dot_or_dotdotW(diriter->current.cFileName));
1300
1301 if (diriter_update_paths(diriter) < 0)
1302 return -1;
1303
1304 return 0;
1305 }
1306
1307 int git_fs_path_diriter_filename(
1308 const char **out,
1309 size_t *out_len,
1310 git_fs_path_diriter *diriter)
1311 {
1312 GIT_ASSERT_ARG(out);
1313 GIT_ASSERT_ARG(out_len);
1314 GIT_ASSERT_ARG(diriter);
1315 GIT_ASSERT(diriter->path_utf8.size > diriter->parent_utf8_len);
1316
1317 *out = &diriter->path_utf8.ptr[diriter->parent_utf8_len+1];
1318 *out_len = diriter->path_utf8.size - diriter->parent_utf8_len - 1;
1319 return 0;
1320 }
1321
1322 int git_fs_path_diriter_fullpath(
1323 const char **out,
1324 size_t *out_len,
1325 git_fs_path_diriter *diriter)
1326 {
1327 GIT_ASSERT_ARG(out);
1328 GIT_ASSERT_ARG(out_len);
1329 GIT_ASSERT_ARG(diriter);
1330
1331 *out = diriter->path_utf8.ptr;
1332 *out_len = diriter->path_utf8.size;
1333 return 0;
1334 }
1335
1336 int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter)
1337 {
1338 GIT_ASSERT_ARG(out);
1339 GIT_ASSERT_ARG(diriter);
1340
1341 return git_win32__file_attribute_to_stat(out,
1342 (WIN32_FILE_ATTRIBUTE_DATA *)&diriter->current,
1343 diriter->path);
1344 }
1345
1346 void git_fs_path_diriter_free(git_fs_path_diriter *diriter)
1347 {
1348 if (diriter == NULL)
1349 return;
1350
1351 git_str_dispose(&diriter->path_utf8);
1352
1353 if (diriter->handle != INVALID_HANDLE_VALUE) {
1354 FindClose(diriter->handle);
1355 diriter->handle = INVALID_HANDLE_VALUE;
1356 }
1357 }
1358
1359 #else
1360
1361 int git_fs_path_diriter_init(
1362 git_fs_path_diriter *diriter,
1363 const char *path,
1364 unsigned int flags)
1365 {
1366 GIT_ASSERT_ARG(diriter);
1367 GIT_ASSERT_ARG(path);
1368
1369 memset(diriter, 0, sizeof(git_fs_path_diriter));
1370
1371 if (git_str_puts(&diriter->path, path) < 0)
1372 return -1;
1373
1374 path_trim_slashes(&diriter->path);
1375
1376 if (diriter->path.size == 0) {
1377 git_error_set(GIT_ERROR_FILESYSTEM, "could not open directory '%s'", path);
1378 return -1;
1379 }
1380
1381 if ((diriter->dir = opendir(diriter->path.ptr)) == NULL) {
1382 git_str_dispose(&diriter->path);
1383
1384 git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path);
1385 return -1;
1386 }
1387
1388 #ifdef GIT_USE_ICONV
1389 if ((flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0)
1390 (void)git_fs_path_iconv_init_precompose(&diriter->ic);
1391 #endif
1392
1393 diriter->parent_len = diriter->path.size;
1394 diriter->flags = flags;
1395
1396 return 0;
1397 }
1398
1399 int git_fs_path_diriter_next(git_fs_path_diriter *diriter)
1400 {
1401 struct dirent *de;
1402 const char *filename;
1403 size_t filename_len;
1404 bool skip_dot = !(diriter->flags & GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT);
1405 int error = 0;
1406
1407 GIT_ASSERT_ARG(diriter);
1408
1409 errno = 0;
1410
1411 do {
1412 if ((de = readdir(diriter->dir)) == NULL) {
1413 if (!errno)
1414 return GIT_ITEROVER;
1415
1416 git_error_set(GIT_ERROR_OS,
1417 "could not read directory '%s'", diriter->path.ptr);
1418 return -1;
1419 }
1420 } while (skip_dot && git_fs_path_is_dot_or_dotdot(de->d_name));
1421
1422 filename = de->d_name;
1423 filename_len = strlen(filename);
1424
1425 #ifdef GIT_USE_ICONV
1426 if ((diriter->flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0 &&
1427 (error = git_fs_path_iconv(&diriter->ic, &filename, &filename_len)) < 0)
1428 return error;
1429 #endif
1430
1431 git_str_truncate(&diriter->path, diriter->parent_len);
1432
1433 if (diriter->parent_len > 0 &&
1434 diriter->path.ptr[diriter->parent_len-1] != '/')
1435 git_str_putc(&diriter->path, '/');
1436
1437 git_str_put(&diriter->path, filename, filename_len);
1438
1439 if (git_str_oom(&diriter->path))
1440 return -1;
1441
1442 return error;
1443 }
1444
1445 int git_fs_path_diriter_filename(
1446 const char **out,
1447 size_t *out_len,
1448 git_fs_path_diriter *diriter)
1449 {
1450 GIT_ASSERT_ARG(out);
1451 GIT_ASSERT_ARG(out_len);
1452 GIT_ASSERT_ARG(diriter);
1453 GIT_ASSERT(diriter->path.size > diriter->parent_len);
1454
1455 *out = &diriter->path.ptr[diriter->parent_len+1];
1456 *out_len = diriter->path.size - diriter->parent_len - 1;
1457 return 0;
1458 }
1459
1460 int git_fs_path_diriter_fullpath(
1461 const char **out,
1462 size_t *out_len,
1463 git_fs_path_diriter *diriter)
1464 {
1465 GIT_ASSERT_ARG(out);
1466 GIT_ASSERT_ARG(out_len);
1467 GIT_ASSERT_ARG(diriter);
1468
1469 *out = diriter->path.ptr;
1470 *out_len = diriter->path.size;
1471 return 0;
1472 }
1473
1474 int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter)
1475 {
1476 GIT_ASSERT_ARG(out);
1477 GIT_ASSERT_ARG(diriter);
1478
1479 return git_fs_path_lstat(diriter->path.ptr, out);
1480 }
1481
1482 void git_fs_path_diriter_free(git_fs_path_diriter *diriter)
1483 {
1484 if (diriter == NULL)
1485 return;
1486
1487 if (diriter->dir) {
1488 closedir(diriter->dir);
1489 diriter->dir = NULL;
1490 }
1491
1492 #ifdef GIT_USE_ICONV
1493 git_fs_path_iconv_clear(&diriter->ic);
1494 #endif
1495
1496 git_str_dispose(&diriter->path);
1497 }
1498
1499 #endif
1500
1501 int git_fs_path_dirload(
1502 git_vector *contents,
1503 const char *path,
1504 size_t prefix_len,
1505 uint32_t flags)
1506 {
1507 git_fs_path_diriter iter = GIT_FS_PATH_DIRITER_INIT;
1508 const char *name;
1509 size_t name_len;
1510 char *dup;
1511 int error;
1512
1513 GIT_ASSERT_ARG(contents);
1514 GIT_ASSERT_ARG(path);
1515
1516 if ((error = git_fs_path_diriter_init(&iter, path, flags)) < 0)
1517 return error;
1518
1519 while ((error = git_fs_path_diriter_next(&iter)) == 0) {
1520 if ((error = git_fs_path_diriter_fullpath(&name, &name_len, &iter)) < 0)
1521 break;
1522
1523 GIT_ASSERT(name_len > prefix_len);
1524
1525 dup = git__strndup(name + prefix_len, name_len - prefix_len);
1526 GIT_ERROR_CHECK_ALLOC(dup);
1527
1528 if ((error = git_vector_insert(contents, dup)) < 0)
1529 break;
1530 }
1531
1532 if (error == GIT_ITEROVER)
1533 error = 0;
1534
1535 git_fs_path_diriter_free(&iter);
1536 return error;
1537 }
1538
1539 int git_fs_path_from_url_or_path(git_str *local_path_out, const char *url_or_path)
1540 {
1541 if (git_fs_path_is_local_file_url(url_or_path))
1542 return git_fs_path_fromurl(local_path_out, url_or_path);
1543 else
1544 return git_str_sets(local_path_out, url_or_path);
1545 }
1546
1547 /* Reject paths like AUX or COM1, or those versions that end in a dot or
1548 * colon. ("AUX." or "AUX:")
1549 */
1550 GIT_INLINE(bool) validate_dospath(
1551 const char *component,
1552 size_t len,
1553 const char dospath[3],
1554 bool trailing_num)
1555 {
1556 size_t last = trailing_num ? 4 : 3;
1557
1558 if (len < last || git__strncasecmp(component, dospath, 3) != 0)
1559 return true;
1560
1561 if (trailing_num && (component[3] < '1' || component[3] > '9'))
1562 return true;
1563
1564 return (len > last &&
1565 component[last] != '.' &&
1566 component[last] != ':');
1567 }
1568
1569 GIT_INLINE(bool) validate_char(unsigned char c, unsigned int flags)
1570 {
1571 if ((flags & GIT_FS_PATH_REJECT_BACKSLASH) && c == '\\')
1572 return false;
1573
1574 if ((flags & GIT_FS_PATH_REJECT_SLASH) && c == '/')
1575 return false;
1576
1577 if (flags & GIT_FS_PATH_REJECT_NT_CHARS) {
1578 if (c < 32)
1579 return false;
1580
1581 switch (c) {
1582 case '<':
1583 case '>':
1584 case ':':
1585 case '"':
1586 case '|':
1587 case '?':
1588 case '*':
1589 return false;
1590 }
1591 }
1592
1593 return true;
1594 }
1595
1596 /*
1597 * We fundamentally don't like some paths when dealing with user-inputted
1598 * strings (to avoid escaping a sandbox): we don't want dot or dot-dot
1599 * anywhere, we want to avoid writing weird paths on Windows that can't
1600 * be handled by tools that use the non-\\?\ APIs, we don't want slashes
1601 * or double slashes at the end of paths that can make them ambiguous.
1602 *
1603 * For checkout, we don't want to recurse into ".git" either.
1604 */
1605 static bool validate_component(
1606 const char *component,
1607 size_t len,
1608 unsigned int flags)
1609 {
1610 if (len == 0)
1611 return !(flags & GIT_FS_PATH_REJECT_EMPTY_COMPONENT);
1612
1613 if ((flags & GIT_FS_PATH_REJECT_TRAVERSAL) &&
1614 len == 1 && component[0] == '.')
1615 return false;
1616
1617 if ((flags & GIT_FS_PATH_REJECT_TRAVERSAL) &&
1618 len == 2 && component[0] == '.' && component[1] == '.')
1619 return false;
1620
1621 if ((flags & GIT_FS_PATH_REJECT_TRAILING_DOT) &&
1622 component[len - 1] == '.')
1623 return false;
1624
1625 if ((flags & GIT_FS_PATH_REJECT_TRAILING_SPACE) &&
1626 component[len - 1] == ' ')
1627 return false;
1628
1629 if ((flags & GIT_FS_PATH_REJECT_TRAILING_COLON) &&
1630 component[len - 1] == ':')
1631 return false;
1632
1633 if (flags & GIT_FS_PATH_REJECT_DOS_PATHS) {
1634 if (!validate_dospath(component, len, "CON", false) ||
1635 !validate_dospath(component, len, "PRN", false) ||
1636 !validate_dospath(component, len, "AUX", false) ||
1637 !validate_dospath(component, len, "NUL", false) ||
1638 !validate_dospath(component, len, "COM", true) ||
1639 !validate_dospath(component, len, "LPT", true))
1640 return false;
1641 }
1642
1643 return true;
1644 }
1645
1646 #ifdef GIT_WIN32
1647 GIT_INLINE(bool) validate_length(
1648 const char *path,
1649 size_t len,
1650 size_t utf8_char_len)
1651 {
1652 GIT_UNUSED(path);
1653 GIT_UNUSED(len);
1654
1655 return (utf8_char_len <= MAX_PATH);
1656 }
1657 #endif
1658
1659 bool git_fs_path_str_is_valid_ext(
1660 const git_str *path,
1661 unsigned int flags,
1662 bool (*validate_char_cb)(char ch, void *payload),
1663 bool (*validate_component_cb)(const char *component, size_t len, void *payload),
1664 bool (*validate_length_cb)(const char *path, size_t len, size_t utf8_char_len),
1665 void *payload)
1666 {
1667 const char *start, *c;
1668 size_t len = 0;
1669
1670 if (!flags)
1671 return true;
1672
1673 for (start = c = path->ptr; *c && len < path->size; c++, len++) {
1674 if (!validate_char(*c, flags))
1675 return false;
1676
1677 if (validate_char_cb && !validate_char_cb(*c, payload))
1678 return false;
1679
1680 if (*c != '/')
1681 continue;
1682
1683 if (!validate_component(start, (c - start), flags))
1684 return false;
1685
1686 if (validate_component_cb &&
1687 !validate_component_cb(start, (c - start), payload))
1688 return false;
1689
1690 start = c + 1;
1691 }
1692
1693 /*
1694 * We want to support paths specified as either `const char *`
1695 * or `git_str *`; we pass size as `SIZE_MAX` when we use a
1696 * `const char *` to avoid a `strlen`. Ensure that we didn't
1697 * have a NUL in the buffer if there was a non-SIZE_MAX length.
1698 */
1699 if (path->size != SIZE_MAX && len != path->size)
1700 return false;
1701
1702 if (!validate_component(start, (c - start), flags))
1703 return false;
1704
1705 if (validate_component_cb &&
1706 !validate_component_cb(start, (c - start), payload))
1707 return false;
1708
1709 #ifdef GIT_WIN32
1710 if ((flags & GIT_FS_PATH_REJECT_LONG_PATHS) != 0) {
1711 size_t utf8_len = git_utf8_char_length(path->ptr, len);
1712
1713 if (!validate_length(path->ptr, len, utf8_len))
1714 return false;
1715
1716 if (validate_length_cb &&
1717 !validate_length_cb(path->ptr, len, utf8_len))
1718 return false;
1719 }
1720 #else
1721 GIT_UNUSED(validate_length_cb);
1722 #endif
1723
1724 return true;
1725 }
1726
1727 int git_fs_path_validate_str_length_with_suffix(
1728 git_str *path,
1729 size_t suffix_len)
1730 {
1731 #ifdef GIT_WIN32
1732 size_t utf8_len = git_utf8_char_length(path->ptr, path->size);
1733 size_t total_len;
1734
1735 if (GIT_ADD_SIZET_OVERFLOW(&total_len, utf8_len, suffix_len) ||
1736 total_len > MAX_PATH) {
1737
1738 git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%.*s'",
1739 (int)path->size, path->ptr);
1740 return -1;
1741 }
1742 #else
1743 GIT_UNUSED(path);
1744 GIT_UNUSED(suffix_len);
1745 #endif
1746
1747 return 0;
1748 }
1749
1750 int git_fs_path_normalize_slashes(git_str *out, const char *path)
1751 {
1752 int error;
1753 char *p;
1754
1755 if ((error = git_str_puts(out, path)) < 0)
1756 return error;
1757
1758 for (p = out->ptr; *p; p++) {
1759 if (*p == '\\')
1760 *p = '/';
1761 }
1762
1763 return 0;
1764 }
1765
1766 bool git_fs_path_supports_symlinks(const char *dir)
1767 {
1768 git_str path = GIT_STR_INIT;
1769 bool supported = false;
1770 struct stat st;
1771 int fd;
1772
1773 if ((fd = git_futils_mktmp(&path, dir, 0666)) < 0 ||
1774 p_close(fd) < 0 ||
1775 p_unlink(path.ptr) < 0 ||
1776 p_symlink("testing", path.ptr) < 0 ||
1777 p_lstat(path.ptr, &st) < 0)
1778 goto done;
1779
1780 supported = (S_ISLNK(st.st_mode) != 0);
1781 done:
1782 if (path.size)
1783 (void)p_unlink(path.ptr);
1784 git_str_dispose(&path);
1785 return supported;
1786 }
1787
1788 static git_fs_path_owner_t mock_owner = GIT_FS_PATH_OWNER_NONE;
1789
1790 void git_fs_path__set_owner(git_fs_path_owner_t owner)
1791 {
1792 mock_owner = owner;
1793 }
1794
1795 #ifdef GIT_WIN32
1796 static PSID *sid_dup(PSID sid)
1797 {
1798 DWORD len;
1799 PSID dup;
1800
1801 len = GetLengthSid(sid);
1802
1803 if ((dup = git__malloc(len)) == NULL)
1804 return NULL;
1805
1806 if (!CopySid(len, dup, sid)) {
1807 git_error_set(GIT_ERROR_OS, "could not duplicate sid");
1808 git__free(dup);
1809 return NULL;
1810 }
1811
1812 return dup;
1813 }
1814
1815 static int current_user_sid(PSID *out)
1816 {
1817 TOKEN_USER *info = NULL;
1818 HANDLE token = NULL;
1819 DWORD len = 0;
1820 int error = -1;
1821
1822 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
1823 git_error_set(GIT_ERROR_OS, "could not lookup process information");
1824 goto done;
1825 }
1826
1827 if (GetTokenInformation(token, TokenUser, NULL, 0, &len) ||
1828 GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
1829 git_error_set(GIT_ERROR_OS, "could not lookup token metadata");
1830 goto done;
1831 }
1832
1833 info = git__malloc(len);
1834 GIT_ERROR_CHECK_ALLOC(info);
1835
1836 if (!GetTokenInformation(token, TokenUser, info, len, &len)) {
1837 git_error_set(GIT_ERROR_OS, "could not lookup current user");
1838 goto done;
1839 }
1840
1841 if ((*out = sid_dup(info->User.Sid)))
1842 error = 0;
1843
1844 done:
1845 if (token)
1846 CloseHandle(token);
1847
1848 git__free(info);
1849 return error;
1850 }
1851
1852 static int file_owner_sid(PSID *out, const char *path)
1853 {
1854 git_win32_path path_w32;
1855 PSECURITY_DESCRIPTOR descriptor = NULL;
1856 PSID owner_sid;
1857 DWORD ret;
1858 int error = -1;
1859
1860 if (git_win32_path_from_utf8(path_w32, path) < 0)
1861 return -1;
1862
1863 ret = GetNamedSecurityInfoW(path_w32, SE_FILE_OBJECT,
1864 OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
1865 &owner_sid, NULL, NULL, NULL, &descriptor);
1866
1867 if (ret == ERROR_FILE_NOT_FOUND || ret == ERROR_PATH_NOT_FOUND)
1868 error = GIT_ENOTFOUND;
1869 else if (ret != ERROR_SUCCESS)
1870 git_error_set(GIT_ERROR_OS, "failed to get security information");
1871 else if (!IsValidSid(owner_sid))
1872 git_error_set(GIT_ERROR_OS, "file owner is not valid");
1873 else if ((*out = sid_dup(owner_sid)))
1874 error = 0;
1875
1876 if (descriptor)
1877 LocalFree(descriptor);
1878
1879 return error;
1880 }
1881
1882 int git_fs_path_owner_is(
1883 bool *out,
1884 const char *path,
1885 git_fs_path_owner_t owner_type)
1886 {
1887 PSID owner_sid = NULL, user_sid = NULL;
1888 BOOL is_admin, admin_owned;
1889 int error;
1890
1891 if (mock_owner) {
1892 *out = ((mock_owner & owner_type) != 0);
1893 return 0;
1894 }
1895
1896 if ((error = file_owner_sid(&owner_sid, path)) < 0)
1897 goto done;
1898
1899 if ((owner_type & GIT_FS_PATH_OWNER_CURRENT_USER) != 0) {
1900 if ((error = current_user_sid(&user_sid)) < 0)
1901 goto done;
1902
1903 if (EqualSid(owner_sid, user_sid)) {
1904 *out = true;
1905 goto done;
1906 }
1907 }
1908
1909 admin_owned =
1910 IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) ||
1911 IsWellKnownSid(owner_sid, WinLocalSystemSid);
1912
1913 if (admin_owned &&
1914 (owner_type & GIT_FS_PATH_OWNER_ADMINISTRATOR) != 0) {
1915 *out = true;
1916 goto done;
1917 }
1918
1919 if (admin_owned &&
1920 (owner_type & GIT_FS_PATH_USER_IS_ADMINISTRATOR) != 0 &&
1921 CheckTokenMembership(NULL, owner_sid, &is_admin) &&
1922 is_admin) {
1923 *out = true;
1924 goto done;
1925 }
1926
1927 *out = false;
1928
1929 done:
1930 git__free(owner_sid);
1931 git__free(user_sid);
1932 return error;
1933 }
1934
1935 #else
1936
1937 static int sudo_uid_lookup(uid_t *out)
1938 {
1939 git_str uid_str = GIT_STR_INIT;
1940 int64_t uid;
1941 int error;
1942
1943 if ((error = git__getenv(&uid_str, "SUDO_UID")) == 0 &&
1944 (error = git__strntol64(&uid, uid_str.ptr, uid_str.size, NULL, 10)) == 0 &&
1945 uid == (int64_t)((uid_t)uid)) {
1946 *out = (uid_t)uid;
1947 }
1948
1949 git_str_dispose(&uid_str);
1950 return error;
1951 }
1952
1953 int git_fs_path_owner_is(
1954 bool *out,
1955 const char *path,
1956 git_fs_path_owner_t owner_type)
1957 {
1958 struct stat st;
1959 uid_t euid, sudo_uid;
1960
1961 if (mock_owner) {
1962 *out = ((mock_owner & owner_type) != 0);
1963 return 0;
1964 }
1965
1966 euid = geteuid();
1967
1968 if (p_lstat(path, &st) != 0) {
1969 if (errno == ENOENT)
1970 return GIT_ENOTFOUND;
1971
1972 git_error_set(GIT_ERROR_OS, "could not stat '%s'", path);
1973 return -1;
1974 }
1975
1976 if ((owner_type & GIT_FS_PATH_OWNER_CURRENT_USER) != 0 &&
1977 st.st_uid == euid) {
1978 *out = true;
1979 return 0;
1980 }
1981
1982 if ((owner_type & GIT_FS_PATH_OWNER_ADMINISTRATOR) != 0 &&
1983 st.st_uid == 0) {
1984 *out = true;
1985 return 0;
1986 }
1987
1988 if ((owner_type & GIT_FS_PATH_OWNER_RUNNING_SUDO) != 0 &&
1989 euid == 0 &&
1990 sudo_uid_lookup(&sudo_uid) == 0 &&
1991 st.st_uid == sudo_uid) {
1992 *out = true;
1993 return 0;
1994 }
1995
1996 *out = false;
1997 return 0;
1998 }
1999
2000 #endif
2001
2002 int git_fs_path_owner_is_current_user(bool *out, const char *path)
2003 {
2004 return git_fs_path_owner_is(out, path, GIT_FS_PATH_OWNER_CURRENT_USER);
2005 }
2006
2007 int git_fs_path_owner_is_system(bool *out, const char *path)
2008 {
2009 return git_fs_path_owner_is(out, path, GIT_FS_PATH_OWNER_ADMINISTRATOR);
2010 }
2011
2012 int git_fs_path_find_executable(git_str *fullpath, const char *executable)
2013 {
2014 #ifdef GIT_WIN32
2015 git_win32_path fullpath_w, executable_w;
2016 int error;
2017
2018 if (git__utf8_to_16(executable_w, GIT_WIN_PATH_MAX, executable) < 0)
2019 return -1;
2020
2021 error = git_win32_path_find_executable(fullpath_w, executable_w);
2022
2023 if (error == 0)
2024 error = git_str_put_w(fullpath, fullpath_w, wcslen(fullpath_w));
2025
2026 return error;
2027 #else
2028 git_str path = GIT_STR_INIT;
2029 const char *current_dir, *term;
2030 bool found = false;
2031
2032 if (git__getenv(&path, "PATH") < 0)
2033 return -1;
2034
2035 current_dir = path.ptr;
2036
2037 while (*current_dir) {
2038 if (! (term = strchr(current_dir, GIT_PATH_LIST_SEPARATOR)))
2039 term = strchr(current_dir, '\0');
2040
2041 git_str_clear(fullpath);
2042 if (git_str_put(fullpath, current_dir, (term - current_dir)) < 0 ||
2043 git_str_putc(fullpath, '/') < 0 ||
2044 git_str_puts(fullpath, executable) < 0)
2045 return -1;
2046
2047 if (git_fs_path_isfile(fullpath->ptr)) {
2048 found = true;
2049 break;
2050 }
2051
2052 current_dir = term;
2053
2054 while (*current_dir == GIT_PATH_LIST_SEPARATOR)
2055 current_dir++;
2056 }
2057
2058 git_str_dispose(&path);
2059
2060 if (found)
2061 return 0;
2062
2063 git_str_clear(fullpath);
2064 return GIT_ENOTFOUND;
2065 #endif
2066 }