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