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 }