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