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 }