145
|
1
|
|
2 /* Copyright (C) 1999-2019 by The D Language Foundation, All Rights Reserved
|
|
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
|
|
9 #include "dsystem.h"
|
|
10 #include "filename.h"
|
|
11
|
|
12 #include "outbuffer.h"
|
|
13 #include "array.h"
|
|
14 #include "file.h"
|
|
15 #include "rmem.h"
|
|
16
|
|
17 #if _WIN32
|
|
18 #include <windows.h>
|
|
19 #endif
|
|
20
|
|
21 #if POSIX
|
|
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");
|
|
113 // Expand ~ only if it is prefixing the rest of the path.
|
|
114 if (!buf.offset && p[1] == '/' && home)
|
|
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
|
|
266 /* falls through */
|
|
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
|
|
428 for (size_t i = 0; i < path->dim; i++)
|
|
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 */
|
|
495 for (size_t i = 0; i < path->dim; i++)
|
|
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);
|
|
547 if (dw == INVALID_FILE_ATTRIBUTES)
|
|
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))
|
|
573 { mem.xfree(const_cast<char *>(p));
|
|
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 {
|
|
626 char *buf = (char *)mem.xmalloc(result);
|
|
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 }
|