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