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