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