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 // 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
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 filename extension (read-only).
180 * Points past '.' of extension.
181 * If there isn't one, return NULL.
182 */
183
ext(const char * str)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
ext()214 const char *FileName::ext()
215 {
216 return ext(str);
217 }
218
219 /********************************
220 * Return mem.xmalloc'd filename with extension removed.
221 */
222
removeExt(const char * str)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
name(const char * str)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
name()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
path(const char * str)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
replaceName(const char * path,const char * name)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
defaultExt(const char * name,const char * ext)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
forceExt(const char * name,const char * ext)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
equalsExt(const char * ext)393 bool FileName::equalsExt(const char *ext)
394 {
395 return equalsExt(str, ext);
396 }
397
equalsExt(const char * name,const char * ext)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
searchPath(Strings * path,const char * name,bool cwd)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
safeSearchPath(Strings * path,const char * name)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
exists(const char * name)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
ensurePathExists(const char * path)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 */
canonicalName(const char * name)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 */
free(const char * str)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
toChars()654 const char *FileName::toChars() const
655 {
656 return str;
657 }
658