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