File Coverage

deps/libgit2/src/path.c
Criterion Covered Total %
statement 406 729 55.6
branch 265 708 37.4
condition n/a
subroutine n/a
pod n/a
total 671 1437 46.6


line stmt bran cond sub pod time code
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
18             #else
19             #include
20             #endif
21             #include
22             #include
23              
24 9801           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 9801 50         if (!(0x80 & (unsigned char)*path))
33 9801 100         return *path && path[1] == ':' ? 2 : 0;
    50          
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 0 0         for (i = 1; i < 4 && (0x80 & (unsigned char)path[i]); i++)
    0          
44             ; /* skip first UTF-8 character */
45 0 0         return path[i] == ':' ? i + 1 : 0;
46             }
47              
48             #ifdef GIT_WIN32
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             */
96 19           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 19 50         if (path == NULL || *path == '\0') {
    50          
103 0           startp = ".";
104 0           len = 1;
105 0           goto Exit;
106             }
107              
108             /* Strip trailing slashes */
109 19           endp = path + strlen(path) - 1;
110 19 50         while (endp > path && *endp == '/')
    50          
111 0           endp--;
112              
113             /* All slashes becomes "/" */
114 19 50         if (endp == path && *endp == '/') {
    0          
115 0           startp = "/";
116 0           len = 1;
117 0           goto Exit;
118             }
119              
120             /* Find the start of the base */
121 19           startp = endp;
122 76 50         while (startp > path && *(startp - 1) != '/')
    100          
123 57           startp--;
124              
125             /* Cast is safe because max path < max int */
126 19           len = (int)(endp - startp + 1);
127              
128             Exit:
129 19           result = len;
130              
131 19 50         if (buffer != NULL && git_buf_set(buffer, startp, len) < 0)
    50          
132 0           return -1;
133              
134 19           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             */
141 2426           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 2426           return -1;
163             }
164              
165             /*
166             * Based on the Android implementation, BSD licensed.
167             * Check http://android.git.kernel.org/
168             */
169 1306           int git_path_dirname_r(git_buf *buffer, const char *path)
170             {
171             const char *endp;
172 1306           int is_prefix = 0, len;
173              
174             /* Empty or NULL string gets treated as "." */
175 1306 50         if (path == NULL || *path == '\0') {
    50          
176 0           path = ".";
177 0           len = 1;
178 0           goto Exit;
179             }
180              
181             /* Strip trailing slashes */
182 1306           endp = path + strlen(path) - 1;
183 1349 50         while (endp > path && *endp == '/')
    100          
184 43           endp--;
185              
186 1306 50         if (endp - path + 1 > INT_MAX) {
187 0           git_error_set(GIT_ERROR_INVALID, "path too long");
188 0           len = -1;
189 0           goto Exit;
190             }
191              
192 1306 50         if ((len = win32_prefix_length(path, (int)(endp - path + 1))) > 0) {
193 0           is_prefix = 1;
194 0           goto Exit;
195             }
196              
197             /* Find the start of the dir */
198 16140 100         while (endp > path && *endp != '/')
    100          
199 14834           endp--;
200              
201             /* Either the dir is "/" or there are no slashes */
202 1306 100         if (endp == path) {
203 186 50         path = (*endp == '/') ? "/" : ".";
204 186           len = 1;
205 186           goto Exit;
206             }
207              
208             do {
209 1120           endp--;
210 1120 50         } while (endp > path && *endp == '/');
    50          
211              
212 1120 50         if (endp - path + 1 > INT_MAX) {
213 0           git_error_set(GIT_ERROR_INVALID, "path too long");
214 0           len = -1;
215 0           goto Exit;
216             }
217              
218 1120 50         if ((len = win32_prefix_length(path, (int)(endp - path + 1))) > 0) {
219 0           is_prefix = 1;
220 0           goto Exit;
221             }
222              
223             /* Cast is safe because max path < max int */
224 1120           len = (int)(endp - path + 1);
225              
226             Exit:
227 1306 50         if (buffer) {
228 1306 50         if (git_buf_set(buffer, path, len) < 0)
229 0           return -1;
230 1306 50         if (is_prefix && git_buf_putc(buffer, '/') < 0)
    0          
231 0           return -1;
232             }
233              
234 1306           return len;
235             }
236              
237              
238 6           char *git_path_dirname(const char *path)
239             {
240 6           git_buf buf = GIT_BUF_INIT;
241             char *dirname;
242              
243 6           git_path_dirname_r(&buf, path);
244 6           dirname = git_buf_detach(&buf);
245 6           git_buf_dispose(&buf); /* avoid memleak if error occurs */
246              
247 6           return dirname;
248             }
249              
250 19           char *git_path_basename(const char *path)
251             {
252 19           git_buf buf = GIT_BUF_INIT;
253             char *basename;
254              
255 19           git_path_basename_r(&buf, path);
256 19           basename = git_buf_detach(&buf);
257 19           git_buf_dispose(&buf); /* avoid memleak if error occurs */
258              
259 19           return basename;
260             }
261              
262 0           size_t git_path_basename_offset(git_buf *buffer)
263             {
264             ssize_t slash;
265              
266 0 0         if (!buffer || buffer->size <= 0)
    0          
267 0           return 0;
268              
269 0           slash = git_buf_rfind_next(buffer, '/');
270              
271 0 0         if (slash >= 0 && buffer->ptr[slash] == '/')
    0          
272 0           return (size_t)(slash + 1);
273              
274 0           return 0;
275             }
276              
277 0           const char *git_path_topdir(const char *path)
278             {
279             size_t len;
280             ssize_t i;
281              
282 0 0         assert(path);
283 0           len = strlen(path);
284              
285 0 0         if (!len || path[len - 1] != '/')
    0          
286 0           return NULL;
287              
288 0 0         for (i = (ssize_t)len - 2; i >= 0; --i)
289 0 0         if (path[i] == '/')
290 0           break;
291              
292 0           return &path[i + 1];
293             }
294              
295 9801           int git_path_root(const char *path)
296             {
297 9801           int offset = 0, prefix_len;
298              
299             /* Does the root of the path look like a windows drive ? */
300 9801 50         if ((prefix_len = dos_drive_prefix_length(path)))
301 0           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 9801 100         if (path[offset] == '/')
320 1705           return offset;
321              
322 8096           return -1; /* Not a real error - signals that path is not rooted */
323             }
324              
325 456           void git_path_trim_slashes(git_buf *path)
326             {
327 456           int ceiling = git_path_root(path->ptr) + 1;
328 456 50         assert(ceiling >= 0);
329              
330 912 50         while (path->size > (size_t)ceiling) {
331 912 100         if (path->ptr[path->size-1] != '/')
332 456           break;
333              
334 456           path->ptr[path->size-1] = '\0';
335 456           path->size--;
336             }
337 456           }
338              
339 2296           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 2296 50         assert(path && path_out);
    50          
345              
346 2296           root = (ssize_t)git_path_root(path);
347              
348 2296 100         if (base != NULL && root < 0) {
    100          
349 2094 50         if (git_buf_joinpath(path_out, base, path) < 0)
350 0           return -1;
351              
352 2094           root = (ssize_t)strlen(base);
353             } else {
354 202 50         if (git_buf_sets(path_out, path) < 0)
355 0           return -1;
356              
357 202 50         if (root < 0)
358 0           root = 0;
359 202 100         else if (base)
360 174           git_path_equal_or_prefixed(base, path, &root);
361             }
362              
363 2296 100         if (root_at)
364 1567           *root_at = root;
365              
366 2296           return 0;
367             }
368              
369 19           void git_path_squash_slashes(git_buf *path)
370             {
371             char *p, *q;
372              
373 19 50         if (path->size == 0)
374 0           return;
375              
376 287 100         for (p = path->ptr, q = path->ptr; *q; p++, q++) {
377 268           *p = *q;
378              
379 268 100         while (*q == '/' && *(q+1) == '/') {
    50          
380 0           path->size--;
381 0           q++;
382             }
383             }
384              
385 19           *p = '\0';
386             }
387              
388 68           int git_path_prettify(git_buf *path_out, const char *path, const char *base)
389             {
390             char buf[GIT_PATH_MAX];
391              
392 68 50         assert(path && path_out);
    50          
393              
394             /* construct path if needed */
395 68 100         if (base != NULL && git_path_root(path) < 0) {
    50          
396 0 0         if (git_buf_joinpath(path_out, base, path) < 0)
397 0           return -1;
398 0           path = path_out->ptr;
399             }
400              
401 68 50         if (p_realpath(path, buf) == NULL) {
402             /* git_error_set resets the errno when dealing with a GIT_ERROR_OS kind of error */
403 0 0         int error = (errno == ENOENT || errno == ENOTDIR) ? GIT_ENOTFOUND : -1;
    0          
404 0           git_error_set(GIT_ERROR_OS, "failed to resolve path '%s'", path);
405              
406 0           git_buf_clear(path_out);
407              
408 0           return error;
409             }
410              
411 68           return git_buf_sets(path_out, buf);
412             }
413              
414 23           int git_path_prettify_dir(git_buf *path_out, const char *path, const char *base)
415             {
416 23           int error = git_path_prettify(path_out, path, base);
417 23 50         return (error < 0) ? error : git_path_to_dir(path_out);
418             }
419              
420 4257           int git_path_to_dir(git_buf *path)
421             {
422 8514           if (path->asize > 0 &&
423 8328 100         git_buf_len(path) > 0 &&
424 4071           path->ptr[git_buf_len(path) - 1] != '/')
425 2411           git_buf_putc(path, '/');
426              
427 4257 50         return git_buf_oom(path) ? -1 : 0;
428             }
429              
430 0           void git_path_string_to_dir(char* path, size_t size)
431             {
432 0           size_t end = strlen(path);
433              
434 0 0         if (end && path[end - 1] != '/' && end < size) {
    0          
    0          
435 0           path[end] = '/';
436 0           path[end + 1] = '\0';
437             }
438 0           }
439              
440 3           int git__percent_decode(git_buf *decoded_out, const char *input)
441             {
442             int len, hi, lo, i;
443 3 50         assert(decoded_out && input);
    50          
444              
445 3           len = (int)strlen(input);
446 3           git_buf_clear(decoded_out);
447              
448 153 100         for(i = 0; i < len; i++)
449             {
450 150           char c = input[i];
451              
452 150 50         if (c != '%')
453 150           goto append;
454              
455 0 0         if (i >= len - 2)
456 0           goto append;
457              
458 0           hi = git__fromhex(input[i + 1]);
459 0           lo = git__fromhex(input[i + 2]);
460              
461 0 0         if (hi < 0 || lo < 0)
    0          
462             goto append;
463              
464 0           c = (char)(hi << 4 | lo);
465 0           i += 2;
466              
467             append:
468 150 50         if (git_buf_putc(decoded_out, c) < 0)
469 0           return -1;
470             }
471              
472 3           return 0;
473             }
474              
475 0           static int error_invalid_local_file_uri(const char *uri)
476             {
477 0           git_error_set(GIT_ERROR_CONFIG, "'%s' is not a valid local file URI", uri);
478 0           return -1;
479             }
480              
481 9           static int local_file_url_prefixlen(const char *file_url)
482             {
483 9           int len = -1;
484              
485 9 100         if (git__prefixcmp(file_url, "file://") == 0) {
486 6 50         if (file_url[7] == '/')
487 6           len = 8;
488 0 0         else if (git__prefixcmp(file_url + 7, "localhost/") == 0)
489 0           len = 17;
490             }
491              
492 9           return len;
493             }
494              
495 6           bool git_path_is_local_file_url(const char *file_url)
496             {
497 6           return (local_file_url_prefixlen(file_url) > 0);
498             }
499              
500 3           int git_path_fromurl(git_buf *local_path_out, const char *file_url)
501             {
502             int offset;
503              
504 3 50         assert(local_path_out && file_url);
    50          
505              
506 3 50         if ((offset = local_file_url_prefixlen(file_url)) < 0 ||
    50          
507 3 50         file_url[offset] == '\0' || file_url[offset] == '/')
508 0           return error_invalid_local_file_uri(file_url);
509              
510             #ifndef GIT_WIN32
511 3           offset--; /* A *nix absolute path starts with a forward slash */
512             #endif
513              
514 3           git_buf_clear(local_path_out);
515 3           return git__percent_decode(local_path_out, file_url + offset);
516             }
517              
518 777           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 777           int error = 0;
525             git_buf iter;
526 777           ssize_t stop = 0, scan;
527 777           char oldc = '\0';
528              
529 777 50         assert(path && cb);
    50          
530              
531 777 50         if (ceiling != NULL) {
532 777 50         if (git__prefixcmp(path->ptr, ceiling) == 0)
533 777           stop = (ssize_t)strlen(ceiling);
534             else
535 0           stop = git_buf_len(path);
536             }
537 777           scan = git_buf_len(path);
538              
539             /* empty path: yield only once */
540 777 50         if (!scan) {
541 0           error = cb(data, "");
542 0 0         if (error)
543 0           git_error_set_after_callback(error);
544 0           return error;
545             }
546              
547 777           iter.ptr = path->ptr;
548 777           iter.size = git_buf_len(path);
549 777           iter.asize = path->asize;
550              
551 1674 100         while (scan >= stop) {
552 923           error = cb(data, iter.ptr);
553 923           iter.ptr[scan] = oldc;
554              
555 923 100         if (error) {
556 26           git_error_set_after_callback(error);
557 26           break;
558             }
559              
560 897           scan = git_buf_rfind_next(&iter, '/');
561 897 50         if (scan >= 0) {
562 897           scan++;
563 897           oldc = iter.ptr[scan];
564 897           iter.size = scan;
565 897           iter.ptr[scan] = '\0';
566             }
567             }
568              
569 777 50         if (scan >= 0)
570 777           iter.ptr[scan] = oldc;
571              
572             /* relative path: yield for the last component */
573 777 100         if (!error && stop == 0 && iter.ptr[0] != '/') {
    50          
    0          
574 0           error = cb(data, "");
575 0 0         if (error)
576 0           git_error_set_after_callback(error);
577             }
578              
579 777           return error;
580             }
581              
582 2880           bool git_path_exists(const char *path)
583             {
584 2880 50         assert(path);
585 2880           return p_access(path, F_OK) == 0;
586             }
587              
588 1911           bool git_path_isdir(const char *path)
589             {
590             struct stat st;
591 1911 100         if (p_stat(path, &st) < 0)
592 336           return false;
593              
594 1911           return S_ISDIR(st.st_mode) != 0;
595             }
596              
597 648           bool git_path_isfile(const char *path)
598             {
599             struct stat st;
600              
601 648 50         assert(path);
602 648 100         if (p_stat(path, &st) < 0)
603 454           return false;
604              
605 648           return S_ISREG(st.st_mode) != 0;
606             }
607              
608 0           bool git_path_islink(const char *path)
609             {
610             struct stat st;
611              
612 0 0         assert(path);
613 0 0         if (p_lstat(path, &st) < 0)
614 0           return false;
615              
616 0           return S_ISLNK(st.st_mode) != 0;
617             }
618              
619             #ifdef GIT_WIN32
620              
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              
664 2           static int path_found_entry(void *payload, git_buf *path)
665             {
666             GIT_UNUSED(payload);
667 2           return !git_path_is_dot_or_dotdot(path->ptr);
668             }
669              
670 2           bool git_path_is_empty_dir(const char *path)
671             {
672             int error;
673 2           git_buf dir = GIT_BUF_INIT;
674              
675 2 50         if (!git_path_isdir(path))
676 0           return false;
677              
678 2 50         if ((error = git_buf_sets(&dir, path)) != 0)
679 0           git_error_clear();
680             else
681 2           error = git_path_direach(&dir, 0, path_found_entry, NULL);
682              
683 2           git_buf_dispose(&dir);
684              
685 2           return !error;
686             }
687              
688             #endif
689              
690 1238           int git_path_set_error(int errno_value, const char *path, const char *action)
691             {
692 1238           switch (errno_value) {
693             case ENOENT:
694             case ENOTDIR:
695 1238           git_error_set(GIT_ERROR_OS, "could not find '%s' to %s", path, action);
696 1238           return GIT_ENOTFOUND;
697              
698             case EINVAL:
699             case ENAMETOOLONG:
700 0           git_error_set(GIT_ERROR_OS, "invalid path for filesystem '%s'", path);
701 0           return GIT_EINVALIDSPEC;
702              
703             case EEXIST:
704 0           git_error_set(GIT_ERROR_OS, "failed %s - '%s' already exists", action, path);
705 0           return GIT_EEXISTS;
706              
707             case EACCES:
708 0           git_error_set(GIT_ERROR_OS, "failed %s - '%s' is locked", action, path);
709 0           return GIT_ELOCKED;
710              
711             default:
712 0           git_error_set(GIT_ERROR_OS, "could not %s '%s'", action, path);
713 0           return -1;
714             }
715             }
716              
717 1229           int git_path_lstat(const char *path, struct stat *st)
718             {
719 1229 50         if (p_lstat(path, st) == 0)
720 1229           return 0;
721              
722 0           return git_path_set_error(errno, path, "stat");
723             }
724              
725 509           static bool _check_dir_contents(
726             git_buf *dir,
727             const char *sub,
728             bool (*predicate)(const char *))
729             {
730             bool result;
731 509           size_t dir_size = git_buf_len(dir);
732 509           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 1018 50         if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, dir_size, sub_size) ||
    50          
737 1018 50         GIT_ADD_SIZET_OVERFLOW(&alloc_size, alloc_size, 2) ||
738 509           git_buf_try_grow(dir, alloc_size, false) < 0)
739 0           return false;
740              
741             /* save excursion */
742 509 50         if (git_buf_joinpath(dir, dir->ptr, sub) < 0)
743 0           return false;
744              
745 509           result = predicate(dir->ptr);
746              
747             /* restore path */
748 509           git_buf_truncate(dir, dir_size);
749 509           return result;
750             }
751              
752 13           bool git_path_contains(git_buf *dir, const char *item)
753             {
754 13           return _check_dir_contents(dir, item, &git_path_exists);
755             }
756              
757 157           bool git_path_contains_dir(git_buf *base, const char *subdir)
758             {
759 157           return _check_dir_contents(base, subdir, &git_path_isdir);
760             }
761              
762 339           bool git_path_contains_file(git_buf *base, const char *file)
763             {
764 339           return _check_dir_contents(base, file, &git_path_isfile);
765             }
766              
767 565           int git_path_find_dir(git_buf *dir, const char *path, const char *base)
768             {
769 565           int error = git_path_join_unrooted(dir, path, base, NULL);
770              
771 565 50         if (!error) {
772             char buf[GIT_PATH_MAX];
773 565 100         if (p_realpath(dir->ptr, buf) != NULL)
774 565           error = git_buf_sets(dir, buf);
775             }
776              
777             /* call dirname if this is not a directory */
778 565 50         if (!error) /* && git_path_isdir(dir->ptr) == false) */
779 565 50         error = (git_path_dirname_r(dir, dir->ptr) < 0) ? -1 : 0;
780              
781 565 50         if (!error)
782 565           error = git_path_to_dir(dir);
783              
784 565           return error;
785             }
786              
787 186           int git_path_resolve_relative(git_buf *path, size_t ceiling)
788             {
789             char *base, *to, *from, *next;
790             size_t len;
791              
792 186 50         GIT_ERROR_CHECK_ALLOC_BUF(path);
    50          
793              
794 186 50         if (ceiling > path->size)
795 0           ceiling = path->size;
796              
797             /* recognize drive prefixes, etc. that should not be backed over */
798 186 50         if (ceiling == 0)
799 186           ceiling = git_path_root(path->ptr) + 1;
800              
801             /* recognize URL prefixes that should not be backed over */
802 186 50         if (ceiling == 0) {
803 186 50         for (next = path->ptr; *next && git__isalpha(*next); ++next);
    50          
804 186 50         if (next[0] == ':' && next[1] == '/' && next[2] == '/')
    0          
    0          
805 0           ceiling = (next + 3) - path->ptr;
806             }
807              
808 186           base = to = from = path->ptr + ceiling;
809              
810 372 100         while (*from) {
811 372 100         for (next = from; *next && *next != '/'; ++next);
    50          
812              
813 186           len = next - from;
814              
815 186 50         if (len == 1 && from[0] == '.')
    50          
816             /* do nothing with singleton dot */;
817              
818 0 0         else if (len == 2 && from[0] == '.' && from[1] == '.') {
    0          
    0          
819             /* error out if trying to up one from a hard base */
820 0 0         if (to == base && ceiling != 0) {
    0          
821 0           git_error_set(GIT_ERROR_INVALID,
822             "cannot strip root component off url");
823 0           return -1;
824             }
825              
826             /* no more path segments to strip,
827             * use '../' as a new base path */
828 0 0         if (to == base) {
829 0 0         if (*next == '/')
830 0           len++;
831              
832 0 0         if (to != from)
833 0           memmove(to, from, len);
834              
835 0           to += len;
836             /* this is now the base, can't back up from a
837             * relative prefix */
838 0           base = to;
839             } else {
840             /* back up a path segment */
841 0 0         while (to > base && to[-1] == '/') to--;
    0          
842 0 0         while (to > base && to[-1] != '/') to--;
    0          
843             }
844             } else {
845 0 0         if (*next == '/' && *from != '/')
    0          
846 0           len++;
847              
848 0 0         if (to != from)
849 0           memmove(to, from, len);
850              
851 0           to += len;
852             }
853              
854 186           from += len;
855              
856 186 50         while (*from == '/') from++;
857             }
858              
859 186           *to = '\0';
860              
861 186           path->size = to - path->ptr;
862              
863 186           return 0;
864             }
865              
866 0           int git_path_apply_relative(git_buf *target, const char *relpath)
867             {
868 0           return git_buf_joinpath(target, git_buf_cstr(target), relpath) ||
869 0           git_path_resolve_relative(target, 0);
870             }
871              
872 220           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 220           size_t len = len1 < len2 ? len1 : len2;
879             int cmp;
880              
881 220           cmp = compare(name1, name2, len);
882 220 100         if (cmp)
883 137           return cmp;
884              
885 83           c1 = name1[len];
886 83           c2 = name2[len];
887              
888 83 100         if (c1 == '\0' && isdir1)
    100          
889 1           c1 = '/';
890              
891 83 100         if (c2 == '\0' && isdir2)
    100          
892 2           c2 = '/';
893              
894 83 100         return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
895             }
896              
897 22           size_t git_path_common_dirlen(const char *one, const char *two)
898             {
899 22           const char *p, *q, *dirsep = NULL;
900              
901 174 100         for (p = one, q = two; *p && *q; p++, q++) {
    50          
902 166 100         if (*p == '/' && *q == '/')
    50          
903 28           dirsep = p;
904 138 100         else if (*p != *q)
905 14           break;
906             }
907              
908 22 100         return dirsep ? (dirsep - one) + 1 : 0;
909             }
910              
911 0           int git_path_make_relative(git_buf *path, const char *parent)
912             {
913             const char *p, *q, *p_dirsep, *q_dirsep;
914 0           size_t plen = path->size, newlen, alloclen, depth = 1, i, offset;
915              
916 0 0         for (p_dirsep = p = path->ptr, q_dirsep = q = parent; *p && *q; p++, q++) {
    0          
917 0 0         if (*p == '/' && *q == '/') {
    0          
918 0           p_dirsep = p;
919 0           q_dirsep = q;
920             }
921 0 0         else if (*p != *q)
922 0           break;
923             }
924              
925             /* need at least 1 common path segment */
926 0 0         if ((p_dirsep == path->ptr || q_dirsep == parent) &&
    0          
    0          
927 0 0         (*p_dirsep != '/' || *q_dirsep != '/')) {
928 0           git_error_set(GIT_ERROR_INVALID,
929             "%s is not a parent of %s", parent, path->ptr);
930 0           return GIT_ENOTFOUND;
931             }
932              
933 0 0         if (*p == '/' && !*q)
    0          
934 0           p++;
935 0 0         else if (!*p && *q == '/')
    0          
936 0           q++;
937 0 0         else if (!*p && !*q)
    0          
938 0           return git_buf_clear(path), 0;
939             else {
940 0           p = p_dirsep + 1;
941 0           q = q_dirsep + 1;
942             }
943              
944 0           plen -= (p - path->ptr);
945              
946 0 0         if (!*q)
947 0           return git_buf_set(path, p, plen);
948              
949 0 0         for (; (q = strchr(q, '/')) && *(q + 1); q++)
    0          
950 0           depth++;
951              
952 0 0         GIT_ERROR_CHECK_ALLOC_MULTIPLY(&newlen, depth, 3);
    0          
953 0 0         GIT_ERROR_CHECK_ALLOC_ADD(&newlen, newlen, plen);
    0          
954              
955 0 0         GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, newlen, 1);
    0          
956              
957             /* save the offset as we might realllocate the pointer */
958 0           offset = p - path->ptr;
959 0 0         if (git_buf_try_grow(path, alloclen, 1) < 0)
960 0           return -1;
961 0           p = path->ptr + offset;
962              
963 0           memmove(path->ptr + (depth * 3), p, plen + 1);
964              
965 0 0         for (i = 0; i < depth; i++)
966 0           memcpy(path->ptr + (i * 3), "../", 3);
967              
968 0           path->size = newlen;
969 0           return 0;
970             }
971              
972 0           bool git_path_has_non_ascii(const char *path, size_t pathlen)
973             {
974 0           const uint8_t *scan = (const uint8_t *)path, *end;
975              
976 0 0         for (end = scan + pathlen; scan < end; ++scan)
977 0 0         if (*scan & 0x80)
978 0           return true;
979              
980 0           return false;
981             }
982              
983             #ifdef GIT_USE_ICONV
984              
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              
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              
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             */
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              
1107 0           bool git_path_does_fs_decompose_unicode(const char *root)
1108             {
1109             GIT_UNUSED(root);
1110 0           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              
1121 338           int git_path_direach(
1122             git_buf *path,
1123             uint32_t flags,
1124             int (*fn)(void *, git_buf *),
1125             void *arg)
1126             {
1127 338           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 338 50         if (git_path_to_dir(path) < 0)
1139 0           return -1;
1140              
1141 338           wd_len = git_buf_len(path);
1142              
1143 338 50         if ((dir = opendir(path->ptr)) == NULL) {
1144 0           git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path->ptr);
1145 0 0         if (errno == ENOENT)
1146 0           return GIT_ENOTFOUND;
1147              
1148 0           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 1283 100         while ((de = readdir(dir)) != NULL) {
1157 947           const char *de_path = de->d_name;
1158 947           size_t de_len = strlen(de_path);
1159              
1160 947 100         if (git_path_is_dot_or_dotdot(de_path))
1161 672           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 275 50         if ((error = git_buf_put(path, de_path, de_len)) < 0)
1169 0           break;
1170              
1171 275           git_error_clear();
1172 275           error = fn(arg, path);
1173              
1174 275           git_buf_truncate(path, wd_len); /* restore path */
1175              
1176             /* Only set our own error if the callback did not set one already */
1177 275 100         if (error != 0) {
1178 2 50         if (!git_error_last())
1179 2           git_error_set_after_callback(error);
1180              
1181 2           break;
1182             }
1183             }
1184              
1185 338           closedir(dir);
1186              
1187             #ifdef GIT_USE_ICONV
1188             git_path_iconv_clear(&ic);
1189             #endif
1190              
1191 338           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              
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              
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              
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              
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              
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              
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              
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              
1359 456           int git_path_diriter_init(
1360             git_path_diriter *diriter,
1361             const char *path,
1362             unsigned int flags)
1363             {
1364 456 50         assert(diriter && path);
    50          
1365              
1366 456           memset(diriter, 0, sizeof(git_path_diriter));
1367              
1368 456 50         if (git_buf_puts(&diriter->path, path) < 0)
1369 0           return -1;
1370              
1371 456           git_path_trim_slashes(&diriter->path);
1372              
1373 456 50         if (diriter->path.size == 0) {
1374 0           git_error_set(GIT_ERROR_FILESYSTEM, "could not open directory '%s'", path);
1375 0           return -1;
1376             }
1377              
1378 456 100         if ((diriter->dir = opendir(diriter->path.ptr)) == NULL) {
1379 2           git_buf_dispose(&diriter->path);
1380              
1381 2           git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path);
1382 2           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 454           diriter->parent_len = diriter->path.size;
1391 454           diriter->flags = flags;
1392              
1393 454           return 0;
1394             }
1395              
1396 1704           int git_path_diriter_next(git_path_diriter *diriter)
1397             {
1398             struct dirent *de;
1399             const char *filename;
1400             size_t filename_len;
1401 1704           bool skip_dot = !(diriter->flags & GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT);
1402 1704           int error = 0;
1403              
1404 1704 50         assert(diriter);
1405              
1406 1704           errno = 0;
1407              
1408             do {
1409 2612 100         if ((de = readdir(diriter->dir)) == NULL) {
1410 454 50         if (!errno)
1411 454           return GIT_ITEROVER;
1412              
1413 0           git_error_set(GIT_ERROR_OS,
1414             "could not read directory '%s'", diriter->path.ptr);
1415 0           return -1;
1416             }
1417 2158 50         } while (skip_dot && git_path_is_dot_or_dotdot(de->d_name));
    100          
1418              
1419 1250           filename = de->d_name;
1420 1250           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 1250           git_buf_truncate(&diriter->path, diriter->parent_len);
1429              
1430 1250 50         if (diriter->parent_len > 0 &&
    50          
1431 1250           diriter->path.ptr[diriter->parent_len-1] != '/')
1432 1250           git_buf_putc(&diriter->path, '/');
1433              
1434 1250           git_buf_put(&diriter->path, filename, filename_len);
1435              
1436 1250 50         if (git_buf_oom(&diriter->path))
1437 0           return -1;
1438              
1439 1250           return error;
1440             }
1441              
1442 0           int git_path_diriter_filename(
1443             const char **out,
1444             size_t *out_len,
1445             git_path_diriter *diriter)
1446             {
1447 0 0         assert(out && out_len && diriter);
    0          
    0          
1448              
1449 0 0         assert(diriter->path.size > diriter->parent_len);
1450              
1451 0           *out = &diriter->path.ptr[diriter->parent_len+1];
1452 0           *out_len = diriter->path.size - diriter->parent_len - 1;
1453 0           return 0;
1454             }
1455              
1456 1250           int git_path_diriter_fullpath(
1457             const char **out,
1458             size_t *out_len,
1459             git_path_diriter *diriter)
1460             {
1461 1250 50         assert(out && out_len && diriter);
    50          
    50          
1462              
1463 1250           *out = diriter->path.ptr;
1464 1250           *out_len = diriter->path.size;
1465 1250           return 0;
1466             }
1467              
1468 1113           int git_path_diriter_stat(struct stat *out, git_path_diriter *diriter)
1469             {
1470 1113 50         assert(out && diriter);
    50          
1471              
1472 1113           return git_path_lstat(diriter->path.ptr, out);
1473             }
1474              
1475 456           void git_path_diriter_free(git_path_diriter *diriter)
1476             {
1477 456 50         if (diriter == NULL)
1478 0           return;
1479              
1480 456 100         if (diriter->dir) {
1481 454           closedir(diriter->dir);
1482 454           diriter->dir = NULL;
1483             }
1484              
1485             #ifdef GIT_USE_ICONV
1486             git_path_iconv_clear(&diriter->ic);
1487             #endif
1488              
1489 456           git_buf_dispose(&diriter->path);
1490             }
1491              
1492             #endif
1493              
1494 2           int git_path_dirload(
1495             git_vector *contents,
1496             const char *path,
1497             size_t prefix_len,
1498             uint32_t flags)
1499             {
1500 2           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 2 50         assert(contents && path);
    50          
1507              
1508 2 50         if ((error = git_path_diriter_init(&iter, path, flags)) < 0)
1509 0           return error;
1510              
1511 6 100         while ((error = git_path_diriter_next(&iter)) == 0) {
1512 4 50         if ((error = git_path_diriter_fullpath(&name, &name_len, &iter)) < 0)
1513 0           break;
1514              
1515 4 50         assert(name_len > prefix_len);
1516              
1517 4           dup = git__strndup(name + prefix_len, name_len - prefix_len);
1518 4 50         GIT_ERROR_CHECK_ALLOC(dup);
1519              
1520 4 50         if ((error = git_vector_insert(contents, dup)) < 0)
1521 0           break;
1522             }
1523              
1524 2 50         if (error == GIT_ITEROVER)
1525 2           error = 0;
1526              
1527 2           git_path_diriter_free(&iter);
1528 2           return error;
1529             }
1530              
1531 6           int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path)
1532             {
1533 6 100         if (git_path_is_local_file_url(url_or_path))
1534 3           return git_path_fromurl(local_path_out, url_or_path);
1535             else
1536 3           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             */
1542 0           GIT_INLINE(bool) verify_dospath(
1543             const char *component,
1544             size_t len,
1545             const char dospath[3],
1546             bool trailing_num)
1547             {
1548 0 0         size_t last = trailing_num ? 4 : 3;
1549              
1550 0 0         if (len < last || git__strncasecmp(component, dospath, 3) != 0)
    0          
1551 0           return true;
1552              
1553 0 0         if (trailing_num && (component[3] < '1' || component[3] > '9'))
    0          
    0          
1554 0           return true;
1555              
1556 0 0         return (len > last &&
1557 0 0         component[last] != '.' &&
    0          
1558 0           component[last] != ':');
1559             }
1560              
1561 0           static int32_t next_hfs_char(const char **in, size_t *len)
1562             {
1563 0 0         while (*len) {
1564             int32_t codepoint;
1565 0           int cp_len = git__utf8_iterate((const uint8_t *)(*in), (int)(*len), &codepoint);
1566 0 0         if (cp_len < 0)
1567 0           return -1;
1568              
1569 0           (*in) += cp_len;
1570 0           (*len) -= cp_len;
1571              
1572             /* these code points are ignored completely */
1573 0 0         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 0           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 0           return git__tolower(codepoint);
1598             }
1599 0           return 0; /* NULL byte -- end of string */
1600             }
1601              
1602 0           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 0 0         if (next_hfs_char(&path, &len) != '.')
1608 0           return true;
1609              
1610 0 0         for (i = 0; i < needle_len; i++) {
1611 0           c = next_hfs_char(&path, &len);
1612 0 0         if (c != needle[i])
1613 0           return true;
1614             }
1615              
1616 0 0         if (next_hfs_char(&path, &len) != '\0')
1617 0           return true;
1618              
1619 0           return false;
1620             }
1621              
1622 0           static bool verify_dotgit_hfs(const char *path, size_t len)
1623             {
1624 0           return verify_dotgit_hfs_generic(path, len, "git", CONST_STRLEN("git"));
1625             }
1626              
1627 535           GIT_INLINE(bool) verify_dotgit_ntfs(git_repository *repo, const char *path, size_t len)
1628             {
1629 535           git_buf *reserved = git_repository__reserved_names_win32;
1630 535           size_t reserved_len = git_repository__reserved_names_win32_len;
1631 535           size_t start = 0, i;
1632              
1633 535 100         if (repo)
1634 370           git_repository__reserved_names(&reserved, &reserved_len, repo, true);
1635              
1636 1579 100         for (i = 0; i < reserved_len; i++) {
1637 1057           git_buf *r = &reserved[i];
1638              
1639 1057 100         if (len >= r->size &&
    100          
1640 836           strncasecmp(path, r->ptr, r->size) == 0) {
1641 13           start = r->size;
1642 13           break;
1643             }
1644             }
1645              
1646 535 100         if (!start)
1647 522           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 13 50         if (path[start] == '\\' || path[start] == ':')
    50          
1655 0           return false;
1656              
1657             /* Reject paths like '.git ' or '.git.' */
1658 13 50         for (i = start; i < len; i++) {
1659 13 50         if (path[i] != ' ' && path[i] != '.')
    50          
1660 13           return true;
1661             }
1662              
1663 535           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             */
1675 0           GIT_INLINE(bool) ntfs_end_of_filename(const char *path)
1676             {
1677 0           const char *c = path;
1678              
1679 0           for (;; c++) {
1680 0 0         if (*c == '\0' || *c == ':')
    0          
1681 0           return true;
1682 0 0         if (*c != ' ' && *c != '.')
    0          
1683 0           return false;
1684 0           }
1685              
1686             return true;
1687             }
1688              
1689 0           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 0 0         if (name[0] == '.' && len >= dotgit_len &&
    0          
    0          
1694 0           !strncasecmp(name + 1, dotgit_name, dotgit_len)) {
1695 0           return !ntfs_end_of_filename(name + dotgit_len + 1);
1696             }
1697              
1698             /* Detect the basic NTFS shortname with the first six chars */
1699 0 0         if (!strncasecmp(name, dotgit_name, 6) && name[6] == '~' &&
    0          
    0          
1700 0 0         name[7] >= '1' && name[7] <= '4')
1701 0           return !ntfs_end_of_filename(name + 8);
1702              
1703             /* Catch fallback names */
1704 0 0         for (i = 0, saw_tilde = 0; i < 8; i++) {
1705 0 0         if (name[i] == '\0') {
1706 0           return true;
1707 0 0         } else if (saw_tilde) {
1708 0 0         if (name[i] < '0' || name[i] > '9')
    0          
1709 0           return true;
1710 0 0         } else if (name[i] == '~') {
1711 0 0         if (name[i+1] < '1' || name[i+1] > '9')
    0          
1712 0           return true;
1713 0           saw_tilde = 1;
1714 0 0         } else if (i >= 6) {
1715 0           return true;
1716 0 0         } else if ((unsigned char)name[i] > 127) {
1717 0           return true;
1718 0 0         } else if (git__tolower(name[i]) != shortname_pfix[i]) {
1719 0           return true;
1720             }
1721             }
1722              
1723 0           return !ntfs_end_of_filename(name + i);
1724             }
1725              
1726 4919           GIT_INLINE(bool) verify_char(unsigned char c, unsigned int flags)
1727             {
1728 4919 50         if ((flags & GIT_PATH_REJECT_BACKSLASH) && c == '\\')
    0          
1729 0           return false;
1730              
1731 4919 100         if ((flags & GIT_PATH_REJECT_SLASH) && c == '/')
    100          
1732 1           return false;
1733              
1734 4918 50         if (flags & GIT_PATH_REJECT_NT_CHARS) {
1735 0 0         if (c < 32)
1736 0           return false;
1737              
1738 0 0         switch (c) {
1739             case '<':
1740             case '>':
1741             case ':':
1742             case '"':
1743             case '|':
1744             case '?':
1745             case '*':
1746 0           return false;
1747             }
1748             }
1749              
1750 4918           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             */
1757 0           GIT_INLINE(size_t) common_prefix_icase(const char *str, size_t len, const char *prefix)
1758             {
1759 0           size_t count = 0;
1760              
1761 0 0         while (len >0 && tolower(*str) == tolower(*prefix)) {
    0          
1762 0           count++;
1763 0           str++;
1764 0           prefix++;
1765 0           len--;
1766             }
1767              
1768 0           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             */
1780 803           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 803 50         if (len == 0)
1788 0           return false;
1789              
1790 803 50         if ((flags & GIT_PATH_REJECT_TRAVERSAL) &&
    100          
1791 7 50         len == 1 && component[0] == '.')
1792 0           return false;
1793              
1794 803 50         if ((flags & GIT_PATH_REJECT_TRAVERSAL) &&
    50          
1795 0 0         len == 2 && component[0] == '.' && component[1] == '.')
    0          
1796 0           return false;
1797              
1798 803 50         if ((flags & GIT_PATH_REJECT_TRAILING_DOT) && component[len-1] == '.')
    0          
1799 0           return false;
1800              
1801 803 50         if ((flags & GIT_PATH_REJECT_TRAILING_SPACE) && component[len-1] == ' ')
    0          
1802 0           return false;
1803              
1804 803 50         if ((flags & GIT_PATH_REJECT_TRAILING_COLON) && component[len-1] == ':')
    0          
1805 0           return false;
1806              
1807 803 50         if (flags & GIT_PATH_REJECT_DOS_PATHS) {
1808 0           if (!verify_dospath(component, len, "CON", false) ||
1809 0 0         !verify_dospath(component, len, "PRN", false) ||
1810 0 0         !verify_dospath(component, len, "AUX", false) ||
1811 0 0         !verify_dospath(component, len, "NUL", false) ||
1812 0 0         !verify_dospath(component, len, "COM", true) ||
1813 0           !verify_dospath(component, len, "LPT", true))
1814 0           return false;
1815             }
1816              
1817 803 50         if (flags & GIT_PATH_REJECT_DOT_GIT_HFS) {
1818 0 0         if (!verify_dotgit_hfs(component, len))
1819 0           return false;
1820 0 0         if (S_ISLNK(mode) && git_path_is_gitfile(component, len, GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_HFS))
    0          
1821 0           return false;
1822             }
1823              
1824 803 100         if (flags & GIT_PATH_REJECT_DOT_GIT_NTFS) {
1825 535 50         if (!verify_dotgit_ntfs(repo, component, len))
1826 0           return false;
1827 535 50         if (S_ISLNK(mode) && git_path_is_gitfile(component, len, GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_NTFS))
    0          
1828 0           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 803 50         if ((flags & GIT_PATH_REJECT_DOT_GIT_HFS) == 0 &&
    100          
1835 268 50         (flags & GIT_PATH_REJECT_DOT_GIT_NTFS) == 0 &&
1836 268           (flags & GIT_PATH_REJECT_DOT_GIT_LITERAL)) {
1837 0 0         if (len >= 4 &&
    0          
1838 0 0         component[0] == '.' &&
1839 0 0         (component[1] == 'g' || component[1] == 'G') &&
    0          
1840 0 0         (component[2] == 'i' || component[2] == 'I') &&
    0          
1841 0 0         (component[3] == 't' || component[3] == 'T')) {
1842 0 0         if (len == 4)
1843 0           return false;
1844              
1845 0 0         if (S_ISLNK(mode) && common_prefix_icase(component, len, ".gitmodules") == len)
    0          
1846 0           return false;
1847             }
1848             }
1849              
1850 803           return true;
1851             }
1852              
1853 463           GIT_INLINE(unsigned int) dotgit_flags(
1854             git_repository *repo,
1855             unsigned int flags)
1856             {
1857 463           int protectHFS = 0, protectNTFS = 1;
1858 463           int error = 0;
1859              
1860 463           flags |= GIT_PATH_REJECT_DOT_GIT_LITERAL;
1861              
1862             #ifdef __APPLE__
1863             protectHFS = 1;
1864             #endif
1865              
1866 463 100         if (repo && !protectHFS)
    50          
1867 351           error = git_repository__configmap_lookup(&protectHFS, repo, GIT_CONFIGMAP_PROTECTHFS);
1868 463 50         if (!error && protectHFS)
    50          
1869 0           flags |= GIT_PATH_REJECT_DOT_GIT_HFS;
1870              
1871 463 100         if (repo)
1872 351           error = git_repository__configmap_lookup(&protectNTFS, repo, GIT_CONFIGMAP_PROTECTNTFS);
1873 463 50         if (!error && protectNTFS)
    50          
1874 463           flags |= GIT_PATH_REJECT_DOT_GIT_NTFS;
1875              
1876 463           return flags;
1877             }
1878              
1879 578           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 578 100         if ((flags & GIT_PATH_REJECT_DOT_GIT))
1889 463           flags = dotgit_flags(repo, flags);
1890              
1891 5496 100         for (start = c = path; *c; c++) {
1892 4919 100         if (!verify_char(*c, flags))
1893 1           return false;
1894              
1895 4918 100         if (*c == '/') {
1896 226 50         if (!verify_component(repo, start, (c - start), mode, flags))
1897 0           return false;
1898              
1899 226           start = c+1;
1900             }
1901             }
1902              
1903 577           return verify_component(repo, start, (c - start), mode, flags);
1904             }
1905              
1906 0           int git_path_normalize_slashes(git_buf *out, const char *path)
1907             {
1908             int error;
1909             char *p;
1910              
1911 0 0         if ((error = git_buf_puts(out, path)) < 0)
1912 0           return error;
1913              
1914 0 0         for (p = out->ptr; *p; p++) {
1915 0 0         if (*p == '\\')
1916 0           *p = '/';
1917             }
1918              
1919 0           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              
1932 0           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 0 0         if (!(gitfile >= GIT_PATH_GITFILE_GITIGNORE && gitfile < ARRAY_SIZE(gitfiles))) {
1938 0           git_error_set(GIT_ERROR_OS, "invalid gitfile for path validation");
1939 0           return -1;
1940             }
1941              
1942 0           file = gitfiles[gitfile].file;
1943 0           filelen = gitfiles[gitfile].filelen;
1944 0           hash = gitfiles[gitfile].hash;
1945              
1946 0           switch (fs) {
1947             case GIT_PATH_FS_GENERIC:
1948 0           return !verify_dotgit_ntfs_generic(path, pathlen, file, filelen, hash) ||
1949 0           !verify_dotgit_hfs_generic(path, pathlen, file, filelen);
1950             case GIT_PATH_FS_NTFS:
1951 0           return !verify_dotgit_ntfs_generic(path, pathlen, file, filelen, hash);
1952             case GIT_PATH_FS_HFS:
1953 0           return !verify_dotgit_hfs_generic(path, pathlen, file, filelen);
1954             default:
1955 0           git_error_set(GIT_ERROR_OS, "invalid filesystem for path validation");
1956 0           return -1;
1957             }
1958             }
1959              
1960 5           bool git_path_supports_symlinks(const char *dir)
1961             {
1962 5           git_buf path = GIT_BUF_INIT;
1963 5           bool supported = false;
1964             struct stat st;
1965             int fd;
1966              
1967 10           if ((fd = git_futils_mktmp(&path, dir, 0666)) < 0 ||
1968 10 50         p_close(fd) < 0 ||
1969 10 50         p_unlink(path.ptr) < 0 ||
1970 10 50         p_symlink("testing", path.ptr) < 0 ||
1971 5           p_lstat(path.ptr, &st) < 0)
1972             goto done;
1973              
1974 5           supported = (S_ISLNK(st.st_mode) != 0);
1975             done:
1976 5 50         if (path.size)
1977 5           (void)p_unlink(path.ptr);
1978 5           git_buf_dispose(&path);
1979 5           return supported;
1980             }
1981              
1982 0           int git_path_validate_system_file_ownership(const char *path)
1983             {
1984             #ifndef GIT_WIN32
1985             GIT_UNUSED(path);
1986 0           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             }