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