xref: /netbsd-src/external/gpl3/gcc.old/dist/gcc/d/dmd/root/filename.c (revision 4c3eb207d36f67d31994830c0a694161fc1ca39b)
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