]>
Commit | Line | Data |
---|---|---|
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 | 24 | static 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 | 71 | int 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 | |
103 | Exit: | |
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 | 116 | int 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 | 170 | Exit: |
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 | ||
180 | char *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 | ||
192 | char *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 |
204 | size_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 | |
219 | const 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 |
237 | int 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 |
264 | void 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 |
278 | int 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 | 308 | int 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 | 334 | int 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 |
340 | int 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 | |
350 | void 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 | 360 | int 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 | ||
387 | append: | |
ab43ad2f RB |
388 | if (git_buf_putc(decoded_out, c) < 0) |
389 | return -1; | |
459e2dcd | 390 | } |
391 | ||
ab43ad2f RB |
392 | return 0; |
393 | } | |
394 | ||
395 | static 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 | 401 | static 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 |
415 | bool 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 |
420 | int 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 | |
438 | int 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 | 502 | bool 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 | 508 | bool 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 | 517 | bool 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 | ||
530 | bool 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 | 573 | static 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 |
579 | bool 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 | 599 | int 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 | ||
622 | int 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 | 630 | static 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 | 656 | bool 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 | 661 | bool 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 | 666 | bool 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 | ||
671 | int 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 |
691 | int 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 | ||
771 | int 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 |
777 | int 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 |
802 | int 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 | 863 | bool 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 | ||
876 | int 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 | ||
883 | void 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 | 892 | int 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 | |
941 | fail: | |
942 | giterr_set(GITERR_OS, "Unable to convert unicode path data"); | |
943 | return -1; | |
219d3457 | 944 | } |
618b7689 | 945 | |
43a04135 RB |
946 | static const char *nfc_file = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D.XXXXXX"; |
947 | static 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 | */ | |
958 | bool 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 | ||
991 | done: | |
992 | git_buf_free(&path); | |
993 | return found_decomposed; | |
994 | } | |
995 | ||
996 | #else | |
997 | ||
998 | bool 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__) | |
1007 | typedef char path_dirent_data[sizeof(struct dirent) + FILENAME_MAX + 1]; | |
1008 | #else | |
1009 | typedef struct dirent path_dirent_data; | |
1010 | #endif | |
1011 | ||
1744fafe RB |
1012 | int 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 | ||
1094 | int 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 | 1141 | static 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 | ||
1173 | int 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 | ||
1193 | int 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 | ||
1207 | int 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 | ||
1219 | int 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 | ||
1228 | void 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 |
1243 | int 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 | 1280 | int 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 |
1322 | int 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 |
1336 | int 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 | ||
1348 | int 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 | ||
1355 | void 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 |
1374 | int 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 |
1411 | int 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 | */ | |
1422 | GIT_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 | 1441 | static 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 | ||
1482 | static 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 |
1494 | GIT_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 |
1529 | GIT_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 | */ | |
1565 | static 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 |
1622 | GIT_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 |
1649 | bool 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 | } |