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 }