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