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