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