]>
Commit | Line | Data |
---|---|---|
b4c522fa | 1 | |
8e788ac6 | 2 | /* Copyright (C) 1999-2020 by The D Language Foundation, All Rights Reserved |
b4c522fa IB |
3 | * http://www.digitalmars.com |
4 | * Distributed under the Boost Software License, Version 1.0. | |
5 | * (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) | |
6 | * https://github.com/D-Programming-Language/dmd/blob/master/src/root/filename.c | |
7 | */ | |
8 | ||
f9ab59ff | 9 | #include "dsystem.h" |
b4c522fa IB |
10 | #include "filename.h" |
11 | ||
b4c522fa IB |
12 | #include "outbuffer.h" |
13 | #include "array.h" | |
14 | #include "file.h" | |
15 | #include "rmem.h" | |
16 | ||
b4c522fa IB |
17 | #if _WIN32 |
18 | #include <windows.h> | |
b4c522fa IB |
19 | #endif |
20 | ||
21 | #if POSIX | |
b4c522fa IB |
22 | #include <utime.h> |
23 | #endif | |
24 | ||
25 | /****************************** FileName ********************************/ | |
26 | ||
27 | FileName::FileName(const char *str) | |
28 | : str(mem.xstrdup(str)) | |
29 | { | |
30 | } | |
31 | ||
32 | const char *FileName::combine(const char *path, const char *name) | |
33 | { char *f; | |
34 | size_t pathlen; | |
35 | size_t namelen; | |
36 | ||
37 | if (!path || !*path) | |
38 | return name; | |
39 | pathlen = strlen(path); | |
40 | namelen = strlen(name); | |
41 | f = (char *)mem.xmalloc(pathlen + 1 + namelen + 1); | |
42 | memcpy(f, path, pathlen); | |
43 | #if POSIX | |
44 | if (path[pathlen - 1] != '/') | |
45 | { f[pathlen] = '/'; | |
46 | pathlen++; | |
47 | } | |
48 | #elif _WIN32 | |
49 | if (path[pathlen - 1] != '\\' && | |
50 | path[pathlen - 1] != '/' && | |
51 | path[pathlen - 1] != ':') | |
52 | { f[pathlen] = '\\'; | |
53 | pathlen++; | |
54 | } | |
55 | #else | |
56 | assert(0); | |
57 | #endif | |
58 | memcpy(f + pathlen, name, namelen + 1); | |
59 | return f; | |
60 | } | |
61 | ||
62 | // Split a path into an Array of paths | |
63 | Strings *FileName::splitPath(const char *path) | |
64 | { | |
65 | char c = 0; // unnecessary initializer is for VC /W4 | |
66 | const char *p; | |
67 | OutBuffer buf; | |
68 | Strings *array; | |
69 | ||
70 | array = new Strings(); | |
71 | if (path) | |
72 | { | |
73 | p = path; | |
74 | do | |
75 | { char instring = 0; | |
76 | ||
77 | while (isspace((utf8_t)*p)) // skip leading whitespace | |
78 | p++; | |
79 | buf.reserve(strlen(p) + 1); // guess size of path | |
80 | for (; ; p++) | |
81 | { | |
82 | c = *p; | |
83 | switch (c) | |
84 | { | |
85 | case '"': | |
86 | instring ^= 1; // toggle inside/outside of string | |
87 | continue; | |
88 | ||
89 | #if MACINTOSH | |
90 | case ',': | |
91 | #endif | |
92 | #if _WIN32 | |
93 | case ';': | |
94 | #endif | |
95 | #if POSIX | |
96 | case ':': | |
97 | #endif | |
98 | p++; | |
99 | break; // note that ; cannot appear as part | |
100 | // of a path, quotes won't protect it | |
101 | ||
102 | case 0x1A: // ^Z means end of file | |
103 | case 0: | |
104 | break; | |
105 | ||
106 | case '\r': | |
107 | continue; // ignore carriage returns | |
108 | ||
109 | #if POSIX | |
110 | case '~': | |
111 | { | |
112 | char *home = getenv("HOME"); | |
134d3a14 IB |
113 | // Expand ~ only if it is prefixing the rest of the path. |
114 | if (!buf.offset && p[1] == '/' && home) | |
b4c522fa IB |
115 | buf.writestring(home); |
116 | else | |
117 | buf.writestring("~"); | |
118 | continue; | |
119 | } | |
120 | #endif | |
121 | ||
122 | default: | |
123 | buf.writeByte(c); | |
124 | continue; | |
125 | } | |
126 | break; | |
127 | } | |
128 | if (buf.offset) // if path is not empty | |
129 | { | |
130 | array->push(buf.extractString()); | |
131 | } | |
132 | } while (c); | |
133 | } | |
134 | return array; | |
135 | } | |
136 | ||
137 | int FileName::compare(RootObject *obj) | |
138 | { | |
139 | return compare(str, ((FileName *)obj)->str); | |
140 | } | |
141 | ||
142 | int FileName::compare(const char *name1, const char *name2) | |
143 | { | |
144 | #if _WIN32 | |
145 | return stricmp(name1, name2); | |
146 | #else | |
147 | return strcmp(name1, name2); | |
148 | #endif | |
149 | } | |
150 | ||
151 | bool FileName::equals(RootObject *obj) | |
152 | { | |
153 | return compare(obj) == 0; | |
154 | } | |
155 | ||
156 | bool FileName::equals(const char *name1, const char *name2) | |
157 | { | |
158 | return compare(name1, name2) == 0; | |
159 | } | |
160 | ||
161 | /************************************ | |
162 | * Return !=0 if absolute path name. | |
163 | */ | |
164 | ||
165 | bool FileName::absolute(const char *name) | |
166 | { | |
167 | #if _WIN32 | |
168 | return (*name == '\\') || | |
169 | (*name == '/') || | |
170 | (*name && name[1] == ':'); | |
171 | #elif POSIX | |
172 | return (*name == '/'); | |
173 | #else | |
174 | assert(0); | |
175 | #endif | |
176 | } | |
177 | ||
178 | /******************************** | |
179 | * Return filename extension (read-only). | |
180 | * Points past '.' of extension. | |
181 | * If there isn't one, return NULL. | |
182 | */ | |
183 | ||
184 | const char *FileName::ext(const char *str) | |
185 | { | |
186 | size_t len = strlen(str); | |
187 | ||
188 | const char *e = str + len; | |
189 | for (;;) | |
190 | { | |
191 | switch (*e) | |
192 | { case '.': | |
193 | return e + 1; | |
194 | #if POSIX | |
195 | case '/': | |
196 | break; | |
197 | #endif | |
198 | #if _WIN32 | |
199 | case '\\': | |
200 | case ':': | |
201 | case '/': | |
202 | break; | |
203 | #endif | |
204 | default: | |
205 | if (e == str) | |
206 | break; | |
207 | e--; | |
208 | continue; | |
209 | } | |
210 | return NULL; | |
211 | } | |
212 | } | |
213 | ||
214 | const char *FileName::ext() | |
215 | { | |
216 | return ext(str); | |
217 | } | |
218 | ||
219 | /******************************** | |
220 | * Return mem.xmalloc'd filename with extension removed. | |
221 | */ | |
222 | ||
223 | const char *FileName::removeExt(const char *str) | |
224 | { | |
225 | const char *e = ext(str); | |
226 | if (e) | |
227 | { size_t len = (e - str) - 1; | |
228 | char *n = (char *)mem.xmalloc(len + 1); | |
229 | memcpy(n, str, len); | |
230 | n[len] = 0; | |
231 | return n; | |
232 | } | |
233 | return mem.xstrdup(str); | |
234 | } | |
235 | ||
236 | /******************************** | |
237 | * Return filename name excluding path (read-only). | |
238 | */ | |
239 | ||
240 | const char *FileName::name(const char *str) | |
241 | { | |
242 | size_t len = strlen(str); | |
243 | ||
244 | const char *e = str + len; | |
245 | for (;;) | |
246 | { | |
247 | switch (*e) | |
248 | { | |
249 | #if POSIX | |
250 | case '/': | |
251 | return e + 1; | |
252 | #endif | |
253 | #if _WIN32 | |
254 | case '/': | |
255 | case '\\': | |
256 | return e + 1; | |
257 | case ':': | |
258 | /* The ':' is a drive letter only if it is the second | |
259 | * character or the last character, | |
260 | * otherwise it is an ADS (Alternate Data Stream) separator. | |
261 | * Consider ADS separators as part of the file name. | |
262 | */ | |
263 | if (e == str + 1 || e == str + len - 1) | |
264 | return e + 1; | |
265 | #endif | |
70d2d777 | 266 | /* falls through */ |
b4c522fa IB |
267 | default: |
268 | if (e == str) | |
269 | break; | |
270 | e--; | |
271 | continue; | |
272 | } | |
273 | return e; | |
274 | } | |
275 | } | |
276 | ||
277 | const char *FileName::name() | |
278 | { | |
279 | return name(str); | |
280 | } | |
281 | ||
282 | /************************************** | |
283 | * Return path portion of str. | |
284 | * Path will does not include trailing path separator. | |
285 | */ | |
286 | ||
287 | const char *FileName::path(const char *str) | |
288 | { | |
289 | const char *n = name(str); | |
290 | size_t pathlen; | |
291 | ||
292 | if (n > str) | |
293 | { | |
294 | #if POSIX | |
295 | if (n[-1] == '/') | |
296 | n--; | |
297 | #elif _WIN32 | |
298 | if (n[-1] == '\\' || n[-1] == '/') | |
299 | n--; | |
300 | #else | |
301 | assert(0); | |
302 | #endif | |
303 | } | |
304 | pathlen = n - str; | |
305 | char *path = (char *)mem.xmalloc(pathlen + 1); | |
306 | memcpy(path, str, pathlen); | |
307 | path[pathlen] = 0; | |
308 | return path; | |
309 | } | |
310 | ||
311 | /************************************** | |
312 | * Replace filename portion of path. | |
313 | */ | |
314 | ||
315 | const char *FileName::replaceName(const char *path, const char *name) | |
316 | { | |
317 | size_t pathlen; | |
318 | size_t namelen; | |
319 | ||
320 | if (absolute(name)) | |
321 | return name; | |
322 | ||
323 | const char *n = FileName::name(path); | |
324 | if (n == path) | |
325 | return name; | |
326 | pathlen = n - path; | |
327 | namelen = strlen(name); | |
328 | char *f = (char *)mem.xmalloc(pathlen + 1 + namelen + 1); | |
329 | memcpy(f, path, pathlen); | |
330 | #if POSIX | |
331 | if (path[pathlen - 1] != '/') | |
332 | { f[pathlen] = '/'; | |
333 | pathlen++; | |
334 | } | |
335 | #elif _WIN32 | |
336 | if (path[pathlen - 1] != '\\' && | |
337 | path[pathlen - 1] != '/' && | |
338 | path[pathlen - 1] != ':') | |
339 | { f[pathlen] = '\\'; | |
340 | pathlen++; | |
341 | } | |
342 | #else | |
343 | assert(0); | |
344 | #endif | |
345 | memcpy(f + pathlen, name, namelen + 1); | |
346 | return f; | |
347 | } | |
348 | ||
349 | /*************************** | |
350 | * Free returned value with FileName::free() | |
351 | */ | |
352 | ||
353 | const char *FileName::defaultExt(const char *name, const char *ext) | |
354 | { | |
355 | const char *e = FileName::ext(name); | |
356 | if (e) // if already has an extension | |
357 | return mem.xstrdup(name); | |
358 | ||
359 | size_t len = strlen(name); | |
360 | size_t extlen = strlen(ext); | |
361 | char *s = (char *)mem.xmalloc(len + 1 + extlen + 1); | |
362 | memcpy(s,name,len); | |
363 | s[len] = '.'; | |
364 | memcpy(s + len + 1, ext, extlen + 1); | |
365 | return s; | |
366 | } | |
367 | ||
368 | /*************************** | |
369 | * Free returned value with FileName::free() | |
370 | */ | |
371 | ||
372 | const char *FileName::forceExt(const char *name, const char *ext) | |
373 | { | |
374 | const char *e = FileName::ext(name); | |
375 | if (e) // if already has an extension | |
376 | { | |
377 | size_t len = e - name; | |
378 | size_t extlen = strlen(ext); | |
379 | ||
380 | char *s = (char *)mem.xmalloc(len + extlen + 1); | |
381 | memcpy(s,name,len); | |
382 | memcpy(s + len, ext, extlen + 1); | |
383 | return s; | |
384 | } | |
385 | else | |
386 | return defaultExt(name, ext); // doesn't have one | |
387 | } | |
388 | ||
389 | /****************************** | |
390 | * Return !=0 if extensions match. | |
391 | */ | |
392 | ||
393 | bool FileName::equalsExt(const char *ext) | |
394 | { | |
395 | return equalsExt(str, ext); | |
396 | } | |
397 | ||
398 | bool FileName::equalsExt(const char *name, const char *ext) | |
399 | { | |
400 | const char *e = FileName::ext(name); | |
401 | if (!e && !ext) | |
402 | return true; | |
403 | if (!e || !ext) | |
404 | return false; | |
405 | return FileName::compare(e, ext) == 0; | |
406 | } | |
407 | ||
408 | /************************************* | |
409 | * Search Path for file. | |
410 | * Input: | |
411 | * cwd if true, search current directory before searching path | |
412 | */ | |
413 | ||
414 | const char *FileName::searchPath(Strings *path, const char *name, bool cwd) | |
415 | { | |
416 | if (absolute(name)) | |
417 | { | |
418 | return exists(name) ? name : NULL; | |
419 | } | |
420 | if (cwd) | |
421 | { | |
422 | if (exists(name)) | |
423 | return name; | |
424 | } | |
425 | if (path) | |
426 | { | |
427 | ||
2cbc99d1 | 428 | for (size_t i = 0; i < path->length; i++) |
b4c522fa IB |
429 | { |
430 | const char *p = (*path)[i]; | |
431 | const char *n = combine(p, name); | |
432 | ||
433 | if (exists(n)) | |
434 | return n; | |
435 | } | |
436 | } | |
437 | return NULL; | |
438 | } | |
439 | ||
440 | ||
441 | /************************************* | |
442 | * Search Path for file in a safe manner. | |
443 | * | |
444 | * Be wary of CWE-22: Improper Limitation of a Pathname to a Restricted Directory | |
445 | * ('Path Traversal') attacks. | |
446 | * http://cwe.mitre.org/data/definitions/22.html | |
447 | * More info: | |
448 | * https://www.securecoding.cert.org/confluence/display/c/FIO02-C.+Canonicalize+path+names+originating+from+tainted+sources | |
449 | * Returns: | |
450 | * NULL file not found | |
451 | * !=NULL mem.xmalloc'd file name | |
452 | */ | |
453 | ||
454 | const char *FileName::safeSearchPath(Strings *path, const char *name) | |
455 | { | |
456 | #if _WIN32 | |
457 | // don't allow leading / because it might be an absolute | |
458 | // path or UNC path or something we'd prefer to just not deal with | |
459 | if (*name == '/') | |
460 | { | |
461 | return NULL; | |
462 | } | |
463 | /* Disallow % \ : and .. in name characters | |
464 | * We allow / for compatibility with subdirectories which is allowed | |
465 | * on dmd/posix. With the leading / blocked above and the rest of these | |
466 | * conservative restrictions, we should be OK. | |
467 | */ | |
468 | for (const char *p = name; *p; p++) | |
469 | { | |
470 | char c = *p; | |
471 | if (c == '\\' || c == ':' || c == '%' || (c == '.' && p[1] == '.')) | |
472 | { | |
473 | return NULL; | |
474 | } | |
475 | } | |
476 | ||
477 | return FileName::searchPath(path, name, false); | |
478 | #elif POSIX | |
479 | /* Even with realpath(), we must check for // and disallow it | |
480 | */ | |
481 | for (const char *p = name; *p; p++) | |
482 | { | |
483 | char c = *p; | |
484 | if (c == '/' && p[1] == '/') | |
485 | { | |
486 | return NULL; | |
487 | } | |
488 | } | |
489 | ||
490 | if (path) | |
491 | { | |
492 | /* Each path is converted to a cannonical name and then a check is done to see | |
493 | * that the searched name is really a child one of the the paths searched. | |
494 | */ | |
2cbc99d1 | 495 | for (size_t i = 0; i < path->length; i++) |
b4c522fa IB |
496 | { |
497 | const char *cname = NULL; | |
498 | const char *cpath = canonicalName((*path)[i]); | |
499 | //printf("FileName::safeSearchPath(): name=%s; path=%s; cpath=%s\n", | |
500 | // name, (char *)path->data[i], cpath); | |
501 | if (cpath == NULL) | |
502 | goto cont; | |
503 | cname = canonicalName(combine(cpath, name)); | |
504 | //printf("FileName::safeSearchPath(): cname=%s\n", cname); | |
505 | if (cname == NULL) | |
506 | goto cont; | |
507 | //printf("FileName::safeSearchPath(): exists=%i " | |
508 | // "strncmp(cpath, cname, %i)=%i\n", exists(cname), | |
509 | // strlen(cpath), strncmp(cpath, cname, strlen(cpath))); | |
510 | // exists and name is *really* a "child" of path | |
511 | if (exists(cname) && strncmp(cpath, cname, strlen(cpath)) == 0) | |
512 | { | |
513 | ::free(const_cast<char *>(cpath)); | |
514 | const char *p = mem.xstrdup(cname); | |
515 | ::free(const_cast<char *>(cname)); | |
516 | return p; | |
517 | } | |
518 | cont: | |
519 | if (cpath) | |
520 | ::free(const_cast<char *>(cpath)); | |
521 | if (cname) | |
522 | ::free(const_cast<char *>(cname)); | |
523 | } | |
524 | } | |
525 | return NULL; | |
526 | #else | |
527 | assert(0); | |
528 | #endif | |
529 | } | |
530 | ||
531 | ||
532 | int FileName::exists(const char *name) | |
533 | { | |
534 | #if POSIX | |
535 | struct stat st; | |
536 | ||
537 | if (stat(name, &st) < 0) | |
538 | return 0; | |
539 | if (S_ISDIR(st.st_mode)) | |
540 | return 2; | |
541 | return 1; | |
542 | #elif _WIN32 | |
543 | DWORD dw; | |
544 | int result; | |
545 | ||
546 | dw = GetFileAttributesA(name); | |
70d2d777 | 547 | if (dw == INVALID_FILE_ATTRIBUTES) |
b4c522fa IB |
548 | result = 0; |
549 | else if (dw & FILE_ATTRIBUTE_DIRECTORY) | |
550 | result = 2; | |
551 | else | |
552 | result = 1; | |
553 | return result; | |
554 | #else | |
555 | assert(0); | |
556 | #endif | |
557 | } | |
558 | ||
559 | bool FileName::ensurePathExists(const char *path) | |
560 | { | |
561 | //printf("FileName::ensurePathExists(%s)\n", path ? path : ""); | |
562 | if (path && *path) | |
563 | { | |
564 | if (!exists(path)) | |
565 | { | |
566 | const char *p = FileName::path(path); | |
567 | if (*p) | |
568 | { | |
569 | #if _WIN32 | |
570 | size_t len = strlen(path); | |
571 | if ((len > 2 && p[-1] == ':' && strcmp(path + 2, p) == 0) || | |
572 | len == strlen(p)) | |
70d2d777 | 573 | { mem.xfree(const_cast<char *>(p)); |
b4c522fa IB |
574 | return 0; |
575 | } | |
576 | #endif | |
577 | bool r = ensurePathExists(p); | |
578 | mem.xfree(const_cast<char *>(p)); | |
579 | if (r) | |
580 | return r; | |
581 | } | |
582 | #if _WIN32 | |
583 | char sep = '\\'; | |
584 | #elif POSIX | |
585 | char sep = '/'; | |
586 | #endif | |
587 | if (path[strlen(path) - 1] != sep) | |
588 | { | |
589 | //printf("mkdir(%s)\n", path); | |
590 | #if _WIN32 | |
591 | int r = _mkdir(path); | |
592 | #endif | |
593 | #if POSIX | |
594 | int r = mkdir(path, (7 << 6) | (7 << 3) | 7); | |
595 | #endif | |
596 | if (r) | |
597 | { | |
598 | /* Don't error out if another instance of dmd just created | |
599 | * this directory | |
600 | */ | |
601 | if (errno != EEXIST) | |
602 | return true; | |
603 | } | |
604 | } | |
605 | } | |
606 | } | |
607 | return false; | |
608 | } | |
609 | ||
610 | /****************************************** | |
611 | * Return canonical version of name in a malloc'd buffer. | |
612 | * This code is high risk. | |
613 | */ | |
614 | const char *FileName::canonicalName(const char *name) | |
615 | { | |
616 | #if POSIX | |
617 | // NULL destination buffer is allowed and preferred | |
618 | return realpath(name, NULL); | |
619 | #elif _WIN32 | |
620 | /* Apparently, there is no good way to do this on Windows. | |
621 | * GetFullPathName isn't it, but use it anyway. | |
622 | */ | |
623 | DWORD result = GetFullPathNameA(name, 0, NULL, NULL); | |
624 | if (result) | |
625 | { | |
70d2d777 | 626 | char *buf = (char *)mem.xmalloc(result); |
b4c522fa IB |
627 | result = GetFullPathNameA(name, result, buf, NULL); |
628 | if (result == 0) | |
629 | { | |
630 | ::free(buf); | |
631 | return NULL; | |
632 | } | |
633 | return buf; | |
634 | } | |
635 | return NULL; | |
636 | #else | |
637 | assert(0); | |
638 | return NULL; | |
639 | #endif | |
640 | } | |
641 | ||
642 | /******************************** | |
643 | * Free memory allocated by FileName routines | |
644 | */ | |
645 | void FileName::free(const char *str) | |
646 | { | |
647 | if (str) | |
648 | { assert(str[0] != (char)0xAB); | |
649 | memset(const_cast<char *>(str), 0xAB, strlen(str) + 1); // stomp | |
650 | } | |
651 | mem.xfree(const_cast<char *>(str)); | |
652 | } | |
653 | ||
654 | const char *FileName::toChars() const | |
655 | { | |
656 | return str; | |
657 | } |