1 // Compiler implementation of the D programming language
2 // Copyright (c) 1999-2015 by Digital Mars
3 // All Rights Reserved
4 // written by Walter Bright
5 // http://www.digitalmars.com
6 // Distributed under the Boost Software License, Version 1.0.
7 // http://www.boost.org/LICENSE_1_0.txt
8 
9 module ddmd.root.filename;
10 
11 import core.stdc.ctype, core.stdc.errno, core.stdc.stdlib, core.stdc..string, core.sys.posix.stdlib, core.sys.posix.sys.stat, core.sys.windows.windows;
12 import ddmd.root.array, ddmd.root.file, ddmd.root.outbuffer, ddmd.root.rmem, ddmd.root.rootobject;
13 
14 version (Windows) extern (C) int mkdir(const char*);
15 version (Windows) alias _mkdir = mkdir;
16 version (Posix) extern (C) char* canonicalize_file_name(const char*);
17 version (Windows) extern (C) int stricmp(const char*, const char*);
18 version (Windows) extern (Windows) DWORD GetFullPathNameA(LPCSTR lpFileName, DWORD nBufferLength, LPSTR lpBuffer, LPSTR* lpFilePart);
19 
20 alias Strings = Array!(const(char)*);
21 alias Files = Array!(File*);
22 
23 /***********************************************************
24  */
25 struct FileName
26 {
27     const(char)* str;
28 
29     extern (D) this(const(char)* str)
30     {
31         this.str = mem.xstrdup(str);
32     }
33 
34     extern (C++) bool equals(const RootObject obj) const
35     {
36         return compare(obj) == 0;
37     }
38 
39     extern (C++) static bool equals(const(char)* name1, const(char)* name2)
40     {
41         return compare(name1, name2) == 0;
42     }
43 
44     extern (C++) int compare(const RootObject obj) const
45     {
46         return compare(str, (cast(FileName*)obj).str);
47     }
48 
49     extern (C++) static int compare(const(char)* name1, const(char)* name2)
50     {
51         version (Windows)
52         {
53             return stricmp(name1, name2);
54         }
55         else
56         {
57             return strcmp(name1, name2);
58         }
59     }
60 
61     /************************************
62      * Return !=0 if absolute path name.
63      */
64     extern (C++) static bool absolute(const(char)* name)
65     {
66         version (Windows)
67         {
68             return (*name == '\\') || (*name == '/') || (*name && name[1] == ':');
69         }
70         else version (Posix)
71         {
72             return (*name == '/');
73         }
74         else
75         {
76             assert(0);
77         }
78     }
79 
80     /********************************
81      * Return filename extension (read-only).
82      * Points past '.' of extension.
83      * If there isn't one, return NULL.
84      */
85     extern (C++) static const(char)* ext(const(char)* str)
86     {
87         size_t len = strlen(str);
88         const(char)* e = str + len;
89         for (;;)
90         {
91             switch (*e)
92             {
93             case '.':
94                 return e + 1;
95                 version (Posix)
96                 {
97                 case '/':
98                     break;
99                 }
100                 version (Windows)
101                 {
102                 case '\\':
103                 case ':':
104                 case '/':
105                     break;
106                 }
107             default:
108                 if (e == str)
109                     break;
110                 e--;
111                 continue;
112             }
113             return null;
114         }
115     }
116 
117     extern (C++) const(char)* ext() const
118     {
119         return ext(str);
120     }
121 
122     /********************************
123      * Return mem.xmalloc'd filename with extension removed.
124      */
125     extern (C++) static const(char)* removeExt(const(char)* str)
126     {
127         const(char)* e = ext(str);
128         if (e)
129         {
130             size_t len = (e - str) - 1;
131             char* n = cast(char*)mem.xmalloc(len + 1);
132             memcpy(n, str, len);
133             n[len] = 0;
134             return n;
135         }
136         return mem.xstrdup(str);
137     }
138 
139     /********************************
140      * Return filename name excluding path (read-only).
141      */
142     extern (C++) static const(char)* name(const(char)* str)
143     {
144         size_t len = strlen(str);
145         const(char)* e = str + len;
146         for (;;)
147         {
148             switch (*e)
149             {
150                 version (Posix)
151                 {
152                 case '/':
153                     return e + 1;
154                 }
155                 version (Windows)
156                 {
157                 case '/':
158                 case '\\':
159                     return e + 1;
160                 case ':':
161                     /* The ':' is a drive letter only if it is the second
162                      * character or the last character,
163                      * otherwise it is an ADS (Alternate Data Stream) separator.
164                      * Consider ADS separators as part of the file name.
165                      */
166                     if (e == str + 1 || e == str + len - 1)
167                         return e + 1;
168                     goto default;
169                 }
170             default:
171                 if (e == str)
172                     break;
173                 e--;
174                 continue;
175             }
176             return e;
177         }
178         assert(0);
179     }
180 
181     extern (C++) const(char)* name() const
182     {
183         return name(str);
184     }
185 
186     /**************************************
187      * Return path portion of str.
188      * Path will does not include trailing path separator.
189      */
190     extern (C++) static const(char)* path(const(char)* str)
191     {
192         const(char)* n = name(str);
193         size_t pathlen;
194         if (n > str)
195         {
196             version (Posix)
197             {
198                 if (n[-1] == '/')
199                     n--;
200             }
201             else version (Windows)
202             {
203                 if (n[-1] == '\\' || n[-1] == '/')
204                     n--;
205             }
206             else
207             {
208                 assert(0);
209             }
210         }
211         pathlen = n - str;
212         char* path = cast(char*)mem.xmalloc(pathlen + 1);
213         memcpy(path, str, pathlen);
214         path[pathlen] = 0;
215         return path;
216     }
217 
218     /**************************************
219      * Replace filename portion of path.
220      */
221     extern (C++) static const(char)* replaceName(const(char)* path, const(char)* name)
222     {
223         size_t pathlen;
224         size_t namelen;
225         if (absolute(name))
226             return name;
227         const(char)* n = FileName.name(path);
228         if (n == path)
229             return name;
230         pathlen = n - path;
231         namelen = strlen(name);
232         char* f = cast(char*)mem.xmalloc(pathlen + 1 + namelen + 1);
233         memcpy(f, path, pathlen);
234         version (Posix)
235         {
236             if (path[pathlen - 1] != '/')
237             {
238                 f[pathlen] = '/';
239                 pathlen++;
240             }
241         }
242         else version (Windows)
243         {
244             if (path[pathlen - 1] != '\\' && path[pathlen - 1] != '/' && path[pathlen - 1] != ':')
245             {
246                 f[pathlen] = '\\';
247                 pathlen++;
248             }
249         }
250         else
251         {
252             assert(0);
253         }
254         memcpy(f + pathlen, name, namelen + 1);
255         return f;
256     }
257 
258     extern (C++) static const(char)* combine(const(char)* path, const(char)* name)
259     {
260         char* f;
261         size_t pathlen;
262         size_t namelen;
263         if (!path || !*path)
264             return cast(char*)name;
265         pathlen = strlen(path);
266         namelen = strlen(name);
267         f = cast(char*)mem.xmalloc(pathlen + 1 + namelen + 1);
268         memcpy(f, path, pathlen);
269         version (Posix)
270         {
271             if (path[pathlen - 1] != '/')
272             {
273                 f[pathlen] = '/';
274                 pathlen++;
275             }
276         }
277         else version (Windows)
278         {
279             if (path[pathlen - 1] != '\\' && path[pathlen - 1] != '/' && path[pathlen - 1] != ':')
280             {
281                 f[pathlen] = '\\';
282                 pathlen++;
283             }
284         }
285         else
286         {
287             assert(0);
288         }
289         memcpy(f + pathlen, name, namelen + 1);
290         return f;
291     }
292 
293     // Split a path into an Array of paths
294     extern (C++) static Strings* splitPath(const(char)* path)
295     {
296         char c = 0; // unnecessary initializer is for VC /W4
297         const(char)* p;
298         OutBuffer buf;
299         Strings* array;
300         array = new Strings();
301         if (path)
302         {
303             p = path;
304             do
305             {
306                 char instring = 0;
307                 while (isspace(cast(char)*p)) // skip leading whitespace
308                     p++;
309                 buf.reserve(strlen(p) + 1); // guess size of path
310                 for (;; p++)
311                 {
312                     c = *p;
313                     switch (c)
314                     {
315                     case '"':
316                         instring ^= 1; // toggle inside/outside of string
317                         continue;
318                         version (OSX)
319                         {
320                         case ',':
321                         }
322                         version (Windows)
323                         {
324                         case ';':
325                         }
326                         version (Posix)
327                         {
328                         case ':':
329                         }
330                         p++;
331                         break;
332                         // note that ; cannot appear as part
333                         // of a path, quotes won't protect it
334                     case 0x1A:
335                         // ^Z means end of file
336                     case 0:
337                         break;
338                     case '\r':
339                         continue;
340                         // ignore carriage returns
341                         version (Posix)
342                         {
343                         case '~':
344                             {
345                                 char* home = getenv("HOME");
346                                 if (home)
347                                     buf.writestring(home);
348                                 else
349                                     buf.writestring("~");
350                                 continue;
351                             }
352                         }
353                         version (none)
354                         {
355                         case ' ':
356                         case '\t':
357                             // tabs in filenames?
358                             if (!instring) // if not in string
359                                 break;
360                             // treat as end of path
361                         }
362                     default:
363                         buf.writeByte(c);
364                         continue;
365                     }
366                     break;
367                 }
368                 if (buf.offset) // if path is not empty
369                 {
370                     array.push(buf.extractString());
371                 }
372             }
373             while (c);
374         }
375         return array;
376     }
377 
378     /***************************
379      * Free returned value with FileName::free()
380      */
381     extern (C++) static const(char)* defaultExt(const(char)* name, const(char)* ext)
382     {
383         const(char)* e = FileName.ext(name);
384         if (e) // if already has an extension
385             return mem.xstrdup(name);
386         size_t len = strlen(name);
387         size_t extlen = strlen(ext);
388         char* s = cast(char*)mem.xmalloc(len + 1 + extlen + 1);
389         memcpy(s, name, len);
390         s[len] = '.';
391         memcpy(s + len + 1, ext, extlen + 1);
392         return s;
393     }
394 
395     /***************************
396      * Free returned value with FileName::free()
397      */
398     extern (C++) static const(char)* forceExt(const(char)* name, const(char)* ext)
399     {
400         const(char)* e = FileName.ext(name);
401         if (e) // if already has an extension
402         {
403             size_t len = e - name;
404             size_t extlen = strlen(ext);
405             char* s = cast(char*)mem.xmalloc(len + extlen + 1);
406             memcpy(s, name, len);
407             memcpy(s + len, ext, extlen + 1);
408             return s;
409         }
410         else
411             return defaultExt(name, ext); // doesn't have one
412     }
413 
414     extern (C++) static bool equalsExt(const(char)* name, const(char)* ext)
415     {
416         const(char)* e = FileName.ext(name);
417         if (!e && !ext)
418             return true;
419         if (!e || !ext)
420             return false;
421         return FileName.compare(e, ext) == 0;
422     }
423 
424     /******************************
425      * Return !=0 if extensions match.
426      */
427     extern (C++) bool equalsExt(const(char)* ext) const
428     {
429         return equalsExt(str, ext);
430     }
431 
432     /*************************************
433      * Search Path for file.
434      * Input:
435      *      cwd     if true, search current directory before searching path
436      */
437     extern (C++) static const(char)* searchPath(Strings* path, const(char)* name, bool cwd)
438     {
439         if (absolute(name))
440         {
441             return exists(name) ? name : null;
442         }
443         if (cwd)
444         {
445             if (exists(name))
446                 return name;
447         }
448         if (path)
449         {
450             for (size_t i = 0; i < path.dim; i++)
451             {
452                 const(char)* p = (*path)[i];
453                 const(char)* n = combine(p, name);
454                 if (exists(n))
455                     return n;
456             }
457         }
458         return null;
459     }
460 
461     /*************************************
462      * Search Path for file in a safe manner.
463      *
464      * Be wary of CWE-22: Improper Limitation of a Pathname to a Restricted Directory
465      * ('Path Traversal') attacks.
466      *      http://cwe.mitre.org/data/definitions/22.html
467      * More info:
468      *      https://www.securecoding.cert.org/confluence/display/seccode/FIO02-C.+Canonicalize+path+names+originating+from+untrusted+sources
469      * Returns:
470      *      NULL    file not found
471      *      !=NULL  mem.xmalloc'd file name
472      */
473     extern (C++) static const(char)* safeSearchPath(Strings* path, const(char)* name)
474     {
475         version (Windows)
476         {
477             /* Disallow % / \ : and .. in name characters
478              */
479             for (const(char)* p = name; *p; p++)
480             {
481                 char c = *p;
482                 if (c == '\\' || c == '/' || c == ':' || c == '%' || (c == '.' && p[1] == '.'))
483                 {
484                     return null;
485                 }
486             }
487             return FileName.searchPath(path, name, false);
488         }
489         else version (Posix)
490         {
491             /* Even with realpath(), we must check for // and disallow it
492              */
493             for (const(char)* p = name; *p; p++)
494             {
495                 char c = *p;
496                 if (c == '/' && p[1] == '/')
497                 {
498                     return null;
499                 }
500             }
501             if (path)
502             {
503                 /* Each path is converted to a cannonical name and then a check is done to see
504                  * that the searched name is really a child one of the the paths searched.
505                  */
506                 for (size_t i = 0; i < path.dim; i++)
507                 {
508                     const(char)* cname = null;
509                     const(char)* cpath = canonicalName((*path)[i]);
510                     //printf("FileName::safeSearchPath(): name=%s; path=%s; cpath=%s\n",
511                     //      name, (char *)path->data[i], cpath);
512                     if (cpath is null)
513                         goto cont;
514                     cname = canonicalName(combine(cpath, name));
515                     //printf("FileName::safeSearchPath(): cname=%s\n", cname);
516                     if (cname is null)
517                         goto cont;
518                     //printf("FileName::safeSearchPath(): exists=%i "
519                     //      "strncmp(cpath, cname, %i)=%i\n", exists(cname),
520                     //      strlen(cpath), strncmp(cpath, cname, strlen(cpath)));
521                     // exists and name is *really* a "child" of path
522                     if (exists(cname) && strncmp(cpath, cname, strlen(cpath)) == 0)
523                     {
524                         .free(cast(void*)cpath);
525                         const(char)* p = mem.xstrdup(cname);
526                         .free(cast(void*)cname);
527                         return p;
528                     }
529                 cont:
530                     if (cpath)
531                         .free(cast(void*)cpath);
532                     if (cname)
533                         .free(cast(void*)cname);
534                 }
535             }
536             return null;
537         }
538         else
539         {
540             assert(0);
541         }
542     }
543 
544     extern (C++) static int exists(const(char)* name)
545     {
546         version (Posix)
547         {
548             stat_t st;
549             if (stat(name, &st) < 0)
550                 return 0;
551             if (S_ISDIR(st.st_mode))
552                 return 2;
553             return 1;
554         }
555         else version (Windows)
556         {
557             DWORD dw;
558             int result;
559             dw = GetFileAttributesA(name);
560             if (dw == -1)
561                 result = 0;
562             else if (dw & FILE_ATTRIBUTE_DIRECTORY)
563                 result = 2;
564             else
565                 result = 1;
566             return result;
567         }
568         else
569         {
570             assert(0);
571         }
572     }
573 
574     extern (C++) static bool ensurePathExists(const(char)* path)
575     {
576         //printf("FileName::ensurePathExists(%s)\n", path ? path : "");
577         if (path && *path)
578         {
579             if (!exists(path))
580             {
581                 const(char)* p = FileName.path(path);
582                 if (*p)
583                 {
584                     version (Windows)
585                     {
586                         size_t len = strlen(path);
587                         if ((len > 2 && p[-1] == ':' && strcmp(path + 2, p) == 0) || len == strlen(p))
588                         {
589                             mem.xfree(cast(void*)p);
590                             return 0;
591                         }
592                     }
593                     bool r = ensurePathExists(p);
594                     mem.xfree(cast(void*)p);
595                     if (r)
596                         return r;
597                 }
598                 version (Windows)
599                 {
600                     char sep = '\\';
601                 }
602                 else version (Posix)
603                 {
604                     char sep = '/';
605                 }
606                 if (path[strlen(path) - 1] != sep)
607                 {
608                     //printf("mkdir(%s)\n", path);
609                     version (Windows)
610                     {
611                         int r = _mkdir(path);
612                     }
613                     version (Posix)
614                     {
615                         int r = mkdir(path, (7 << 6) | (7 << 3) | 7);
616                     }
617                     if (r)
618                     {
619                         /* Don't error out if another instance of dmd just created
620                          * this directory
621                          */
622                         if (errno != EEXIST)
623                             return true;
624                     }
625                 }
626             }
627         }
628         return false;
629     }
630 
631     /******************************************
632      * Return canonical version of name in a malloc'd buffer.
633      * This code is high risk.
634      */
635     extern (C++) static const(char)* canonicalName(const(char)* name)
636     {
637         version (Posix)
638         {
639             // NULL destination buffer is allowed and preferred
640             return realpath(name, null);
641         }
642         else version (Windows)
643         {
644             /* Apparently, there is no good way to do this on Windows.
645              * GetFullPathName isn't it, but use it anyway.
646              */
647             DWORD result = GetFullPathNameA(name, 0, null, null);
648             if (result)
649             {
650                 char* buf = cast(char*)malloc(result);
651                 result = GetFullPathNameA(name, result, buf, null);
652                 if (result == 0)
653                 {
654                     .free(buf);
655                     return null;
656                 }
657                 return buf;
658             }
659             return null;
660         }
661         else
662         {
663             assert(0);
664         }
665     }
666 
667     /********************************
668      * Free memory allocated by FileName routines
669      */
670     extern (C++) static void free(const(char)* str)
671     {
672         if (str)
673         {
674             assert(str[0] != cast(char)0xAB);
675             memset(cast(void*)str, 0xAB, strlen(str) + 1); // stomp
676         }
677         mem.xfree(cast(void*)str);
678     }
679 
680     extern (C++) const(char)* toChars() const
681     {
682         return cast(char*)str; // toChars() should really be const
683     }
684 }