1
2 /* Copyright (C) 1999-2021 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 #include "port.h"
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
FileName(const char * str)27 FileName::FileName(const char *str)
28 : str(mem.xstrdup(str))
29 {
30 }
31
combine(const char * path,const char * name)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
splitPath(const char * path)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.length() && 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.length()) // if path is not empty
129 {
130 array->push(buf.extractChars());
131 }
132 } while (c);
133 }
134 return array;
135 }
136
compare(RootObject * obj)137 int FileName::compare(RootObject *obj)
138 {
139 return compare(str, ((FileName *)obj)->str);
140 }
141
compare(const char * name1,const char * name2)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
equals(RootObject * obj)151 bool FileName::equals(RootObject *obj)
152 {
153 return compare(obj) == 0;
154 }
155
equals(const char * name1,const char * name2)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
absolute(const char * name)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 the given name as an absolute path
180
181 Params:
182 name = path
183 base = the absolute base to prefix name with if it is relative
184
185 Returns: name as an absolute path relative to base
186 */
toAbsolute(const char * name,const char * base)187 const char *FileName::toAbsolute(const char *name, const char *base)
188 {
189 return absolute(name) ? name : combine(base ? base : getcwd(NULL, 0), name);
190 }
191
192 /********************************
193 * Return filename extension (read-only).
194 * Points past '.' of extension.
195 * If there isn't one, return NULL.
196 */
197
ext(const char * str)198 const char *FileName::ext(const char *str)
199 {
200 size_t len = strlen(str);
201
202 const char *e = str + len;
203 for (;;)
204 {
205 switch (*e)
206 { case '.':
207 return e + 1;
208 #if POSIX
209 case '/':
210 break;
211 #endif
212 #if _WIN32
213 case '\\':
214 case ':':
215 case '/':
216 break;
217 #endif
218 default:
219 if (e == str)
220 break;
221 e--;
222 continue;
223 }
224 return NULL;
225 }
226 }
227
ext()228 const char *FileName::ext()
229 {
230 return ext(str);
231 }
232
233 /********************************
234 * Return mem.xmalloc'd filename with extension removed.
235 */
236
removeExt(const char * str)237 const char *FileName::removeExt(const char *str)
238 {
239 const char *e = ext(str);
240 if (e)
241 { size_t len = (e - str) - 1;
242 char *n = (char *)mem.xmalloc(len + 1);
243 memcpy(n, str, len);
244 n[len] = 0;
245 return n;
246 }
247 return mem.xstrdup(str);
248 }
249
250 /********************************
251 * Return filename name excluding path (read-only).
252 */
253
name(const char * str)254 const char *FileName::name(const char *str)
255 {
256 size_t len = strlen(str);
257
258 const char *e = str + len;
259 for (;;)
260 {
261 switch (*e)
262 {
263 #if POSIX
264 case '/':
265 return e + 1;
266 #endif
267 #if _WIN32
268 case '/':
269 case '\\':
270 return e + 1;
271 case ':':
272 /* The ':' is a drive letter only if it is the second
273 * character or the last character,
274 * otherwise it is an ADS (Alternate Data Stream) separator.
275 * Consider ADS separators as part of the file name.
276 */
277 if (e == str + 1 || e == str + len - 1)
278 return e + 1;
279 #endif
280 /* falls through */
281 default:
282 if (e == str)
283 break;
284 e--;
285 continue;
286 }
287 return e;
288 }
289 }
290
name()291 const char *FileName::name()
292 {
293 return name(str);
294 }
295
296 /**************************************
297 * Return path portion of str.
298 * Path will does not include trailing path separator.
299 */
300
path(const char * str)301 const char *FileName::path(const char *str)
302 {
303 const char *n = name(str);
304 size_t pathlen;
305
306 if (n > str)
307 {
308 #if POSIX
309 if (n[-1] == '/')
310 n--;
311 #elif _WIN32
312 if (n[-1] == '\\' || n[-1] == '/')
313 n--;
314 #else
315 assert(0);
316 #endif
317 }
318 pathlen = n - str;
319 char *path = (char *)mem.xmalloc(pathlen + 1);
320 memcpy(path, str, pathlen);
321 path[pathlen] = 0;
322 return path;
323 }
324
325 /**************************************
326 * Replace filename portion of path.
327 */
328
replaceName(const char * path,const char * name)329 const char *FileName::replaceName(const char *path, const char *name)
330 {
331 size_t pathlen;
332 size_t namelen;
333
334 if (absolute(name))
335 return name;
336
337 const char *n = FileName::name(path);
338 if (n == path)
339 return name;
340 pathlen = n - path;
341 namelen = strlen(name);
342 char *f = (char *)mem.xmalloc(pathlen + 1 + namelen + 1);
343 memcpy(f, path, pathlen);
344 #if POSIX
345 if (path[pathlen - 1] != '/')
346 { f[pathlen] = '/';
347 pathlen++;
348 }
349 #elif _WIN32
350 if (path[pathlen - 1] != '\\' &&
351 path[pathlen - 1] != '/' &&
352 path[pathlen - 1] != ':')
353 { f[pathlen] = '\\';
354 pathlen++;
355 }
356 #else
357 assert(0);
358 #endif
359 memcpy(f + pathlen, name, namelen + 1);
360 return f;
361 }
362
363 /***************************
364 * Free returned value with FileName::free()
365 */
366
defaultExt(const char * name,const char * ext)367 const char *FileName::defaultExt(const char *name, const char *ext)
368 {
369 const char *e = FileName::ext(name);
370 if (e) // if already has an extension
371 return mem.xstrdup(name);
372
373 size_t len = strlen(name);
374 size_t extlen = strlen(ext);
375 char *s = (char *)mem.xmalloc(len + 1 + extlen + 1);
376 memcpy(s,name,len);
377 s[len] = '.';
378 memcpy(s + len + 1, ext, extlen + 1);
379 return s;
380 }
381
382 /***************************
383 * Free returned value with FileName::free()
384 */
385
forceExt(const char * name,const char * ext)386 const char *FileName::forceExt(const char *name, const char *ext)
387 {
388 const char *e = FileName::ext(name);
389 if (e) // if already has an extension
390 {
391 size_t len = e - name;
392 size_t extlen = strlen(ext);
393
394 char *s = (char *)mem.xmalloc(len + extlen + 1);
395 memcpy(s,name,len);
396 memcpy(s + len, ext, extlen + 1);
397 return s;
398 }
399 else
400 return defaultExt(name, ext); // doesn't have one
401 }
402
403 /******************************
404 * Return !=0 if extensions match.
405 */
406
equalsExt(const char * ext)407 bool FileName::equalsExt(const char *ext)
408 {
409 return equalsExt(str, ext);
410 }
411
equalsExt(const char * name,const char * ext)412 bool FileName::equalsExt(const char *name, const char *ext)
413 {
414 const char *e = FileName::ext(name);
415 if (!e && !ext)
416 return true;
417 if (!e || !ext)
418 return false;
419 return FileName::compare(e, ext) == 0;
420 }
421
422 /*************************************
423 * Search Path for file.
424 * Input:
425 * cwd if true, search current directory before searching path
426 */
427
searchPath(Strings * path,const char * name,bool cwd)428 const char *FileName::searchPath(Strings *path, const char *name, bool cwd)
429 {
430 if (absolute(name))
431 {
432 return exists(name) ? name : NULL;
433 }
434 if (cwd)
435 {
436 if (exists(name))
437 return name;
438 }
439 if (path)
440 {
441
442 for (size_t i = 0; i < path->length; i++)
443 {
444 const char *p = (*path)[i];
445 const char *n = combine(p, name);
446
447 if (exists(n))
448 return n;
449 }
450 }
451 return NULL;
452 }
453
454
455 /*************************************
456 * Search Path for file in a safe manner.
457 *
458 * Be wary of CWE-22: Improper Limitation of a Pathname to a Restricted Directory
459 * ('Path Traversal') attacks.
460 * http://cwe.mitre.org/data/definitions/22.html
461 * More info:
462 * https://www.securecoding.cert.org/confluence/display/c/FIO02-C.+Canonicalize+path+names+originating+from+tainted+sources
463 * Returns:
464 * NULL file not found
465 * !=NULL mem.xmalloc'd file name
466 */
467
safeSearchPath(Strings * path,const char * name)468 const char *FileName::safeSearchPath(Strings *path, const char *name)
469 {
470 #if _WIN32
471 // don't allow leading / because it might be an absolute
472 // path or UNC path or something we'd prefer to just not deal with
473 if (*name == '/')
474 {
475 return NULL;
476 }
477 /* Disallow % \ : and .. in name characters
478 * We allow / for compatibility with subdirectories which is allowed
479 * on dmd/posix. With the leading / blocked above and the rest of these
480 * conservative restrictions, we should be OK.
481 */
482 for (const char *p = name; *p; p++)
483 {
484 char c = *p;
485 if (c == '\\' || c == ':' || c == '%' || (c == '.' && p[1] == '.'))
486 {
487 return NULL;
488 }
489 }
490
491 return FileName::searchPath(path, name, false);
492 #elif POSIX
493 /* Even with realpath(), we must check for // and disallow it
494 */
495 for (const char *p = name; *p; p++)
496 {
497 char c = *p;
498 if (c == '/' && p[1] == '/')
499 {
500 return NULL;
501 }
502 }
503
504 if (path)
505 {
506 /* Each path is converted to a cannonical name and then a check is done to see
507 * that the searched name is really a child one of the the paths searched.
508 */
509 for (size_t i = 0; i < path->length; i++)
510 {
511 const char *cname = NULL;
512 const char *cpath = canonicalName((*path)[i]);
513 //printf("FileName::safeSearchPath(): name=%s; path=%s; cpath=%s\n",
514 // name, (char *)path->data[i], cpath);
515 if (cpath == NULL)
516 goto cont;
517 cname = canonicalName(combine(cpath, name));
518 //printf("FileName::safeSearchPath(): cname=%s\n", cname);
519 if (cname == NULL)
520 goto cont;
521 //printf("FileName::safeSearchPath(): exists=%i "
522 // "strncmp(cpath, cname, %i)=%i\n", exists(cname),
523 // strlen(cpath), strncmp(cpath, cname, strlen(cpath)));
524 // exists and name is *really* a "child" of path
525 if (exists(cname) && strncmp(cpath, cname, strlen(cpath)) == 0)
526 {
527 ::free(const_cast<char *>(cpath));
528 const char *p = mem.xstrdup(cname);
529 ::free(const_cast<char *>(cname));
530 return p;
531 }
532 cont:
533 if (cpath)
534 ::free(const_cast<char *>(cpath));
535 if (cname)
536 ::free(const_cast<char *>(cname));
537 }
538 }
539 return NULL;
540 #else
541 assert(0);
542 #endif
543 }
544
545
exists(const char * name)546 int FileName::exists(const char *name)
547 {
548 #if POSIX
549 struct stat st;
550
551 if (stat(name, &st) < 0)
552 return 0;
553 if (S_ISDIR(st.st_mode))
554 return 2;
555 return 1;
556 #elif _WIN32
557 DWORD dw;
558 int result;
559
560 dw = GetFileAttributesA(name);
561 if (dw == INVALID_FILE_ATTRIBUTES)
562 result = 0;
563 else if (dw & FILE_ATTRIBUTE_DIRECTORY)
564 result = 2;
565 else
566 result = 1;
567 return result;
568 #else
569 assert(0);
570 #endif
571 }
572
ensurePathExists(const char * path)573 bool FileName::ensurePathExists(const char *path)
574 {
575 //printf("FileName::ensurePathExists(%s)\n", path ? path : "");
576 if (path && *path)
577 {
578 if (!exists(path))
579 {
580 const char *p = FileName::path(path);
581 if (*p)
582 {
583 #if _WIN32
584 size_t len = strlen(path);
585 if ((len > 2 && p[-1] == ':' && strcmp(path + 2, p) == 0) ||
586 len == strlen(p))
587 { mem.xfree(const_cast<char *>(p));
588 return 0;
589 }
590 #endif
591 bool r = ensurePathExists(p);
592 mem.xfree(const_cast<char *>(p));
593 if (r)
594 return r;
595 }
596 #if _WIN32
597 char sep = '\\';
598 #elif POSIX
599 char sep = '/';
600 #endif
601 if (path[strlen(path) - 1] != sep)
602 {
603 //printf("mkdir(%s)\n", path);
604 #if _WIN32
605 int r = _mkdir(path);
606 #endif
607 #if POSIX
608 int r = mkdir(path, (7 << 6) | (7 << 3) | 7);
609 #endif
610 if (r)
611 {
612 /* Don't error out if another instance of dmd just created
613 * this directory
614 */
615 if (errno != EEXIST)
616 return true;
617 }
618 }
619 }
620 }
621 return false;
622 }
623
624 /******************************************
625 * Return canonical version of name in a malloc'd buffer.
626 * This code is high risk.
627 */
canonicalName(const char * name)628 const char *FileName::canonicalName(const char *name)
629 {
630 #if POSIX
631 // NULL destination buffer is allowed and preferred
632 return realpath(name, NULL);
633 #elif _WIN32
634 /* Apparently, there is no good way to do this on Windows.
635 * GetFullPathName isn't it, but use it anyway.
636 */
637 DWORD result = GetFullPathNameA(name, 0, NULL, NULL);
638 if (result)
639 {
640 char *buf = (char *)mem.xmalloc(result);
641 result = GetFullPathNameA(name, result, buf, NULL);
642 if (result == 0)
643 {
644 ::free(buf);
645 return NULL;
646 }
647 return buf;
648 }
649 return NULL;
650 #else
651 assert(0);
652 return NULL;
653 #endif
654 }
655
656 /********************************
657 * Free memory allocated by FileName routines
658 */
free(const char * str)659 void FileName::free(const char *str)
660 {
661 if (str)
662 { assert(str[0] != (char)0xAB);
663 memset(const_cast<char *>(str), 0xAB, strlen(str) + 1); // stomp
664 }
665 mem.xfree(const_cast<char *>(str));
666 }
667
toChars()668 const char *FileName::toChars() const
669 {
670 return str;
671 }
672