1%% options 2 3copyright owner = Dirk Krause 4copyright year = 2015-xxxx 5SPDX-License-Identifier: BSD-3-Clause 6 7 8%% header 9 10/** @file 11 String operations for path names 12 (wchar_t characters). 13*/ 14 15#ifndef DK4CONF_H_INCLUDED 16#if DK4_BUILDING_DKTOOLS4 17#include "dk4conf.h" 18#else 19#include <dktools-4/dk4conf.h> 20#endif 21#endif 22 23#ifndef DK4TYPES_H_INCLUDED 24#if DK4_BUILDING_DKTOOLS4 25#include <libdk4base/dk4types.h> 26#else 27#include <dktools-4/dk4types.h> 28#endif 29#endif 30 31#ifndef DK4ERROR_H_INCLUDED 32#if DK4_BUILDING_DKTOOLS4 33#include <libdk4base/dk4error.h> 34#else 35#include <dktools-4/dk4error.h> 36#endif 37#endif 38 39#ifdef __cplusplus 40extern "C" { 41#endif 42 43/** Check whether a file name is an absolute path. 44 CRT on Windows: Not used. 45 @param path Path name to check. 46 @return 1 for absolute path, 0 for other path. 47*/ 48int 49dk4pathw_is_absolute(const wchar_t *path); 50 51/** Check whether a file name is a relative path. 52 CRT on Windows: Not used. 53 @param path Path name to check. 54 @return 1 for absolute path, 0 for other path. 55*/ 56int 57dk4pathw_is_relative(const wchar_t *path); 58 59/** Append path filename to path name already stored in buffer. 60 Resolve . and .. for current directory and parent directory. 61 CRT on Windows: Optional, disabling CRT degrades performance. 62 @param buffer Buffer already containing a path. 63 @param sz Buffer size. 64 @param filename Relative file name to append to buffer. 65 @param erp Error report, may be NULL. 66 @return 1 on success, 0 on error. 67 68 Error codes: 69 - DK4_E_INVALID_ARGUMENTS<br> 70 if buffer or filename is NULL or sz is 0, 71 - DK4_E_MATH_OVERFLOW<br> 72 if filename is too long, 73 - DK4_E_MEMORY<br> 74 if allocation of a filename copy fails, 75 - DK4_E_SYNTAX<br> 76 if too many .. in filename, 77 - DK4_E_BUFFER_TOO_SMALL<br> 78 if buffer size is too small. 79*/ 80int 81dk4pathw_append( 82 wchar_t *buffer, size_t sz, const wchar_t *filename, dk4_er_t *erp 83); 84 85/** Find pointer to suffix. 86 CRT on Windows: Not used. 87 @param filename File name to find suffix for. 88 @param erp Error report, may be NULL. 89 @return Pointer to suffix dot if found, NULL otherwise. 90 91 Error codes: 92 - DK4_E_INVALID_ARGUMENTS<br> 93 if filename is NULL, 94 - DK4_E_NOT_FOUND<br> 95 if the file name does not contain a suffix. 96*/ 97wchar_t * 98dk4pathw_get_suffix(const wchar_t *filename, dk4_er_t *erp); 99 100/** Correct file name separators from slash to backslash on Windows, 101 vice versa on other systems. 102 CRT on Windows: Not used. 103 @param filename File name to correct. 104*/ 105void 106dk4pathw_correct_sep(wchar_t *filename); 107 108/** Check whether file name needs expansion on Windows. 109 CRT on Windows: Not used. 110 @param filename File name to check. 111 @return 1 if expansion is necessary, 0 otherwise. 112*/ 113int 114dk4pathw_must_expand(const wchar_t *filename); 115 116/** Create file name with specified suffix. 117 @param pdst Destination buffer. 118 @param szdst Size of pdst (number of wchar_t). 119 @param srcname Source file name. 120 @param newsu New file suffix. 121 @param erp Error report, may be NULL. 122 @return 1 on success, 0 on error. 123 124 Error codes: 125 - DK4_E_INVALID_ARGUMENTS<br> 126 if pdst or srcname or newsu is NULL or szdst is 0, 127 - DK4_E_BUFFER_TOO_SMALL<br> 128 if the dst buffer is too small. 129 - DK4_E_MATH_OVERFLOW<br> 130 if a mathematical overflow occured in size calculation, 131*/ 132int 133dk4pathw_set_suffix( 134 wchar_t *pdst, 135 size_t szdst, 136 wchar_t const *srcname, 137 wchar_t const *newsu, 138 dk4_er_t *erp 139); 140 141/** Create a dynamic copy of a file name with changed suffix. 142 @param srcname Old file name. 143 @param newsu New suffix. 144 @param erp Error report, may be NULL. 145 @return Valid pointer to changed file name on success, NULL on error. 146 On success call dk4mem_free() on the pointer when no longer needed 147 to release the memory. 148 149 Error codes: 150 - DK4_E_INVALID_ARGUMENTS<br> 151 if srcname or newsu is NULL, 152 - DK4_E_BUG<br> 153 if a bug occured, 154 - DK4_E_MATH_OVERFLOW<br> 155 if a mathematical overflow occured in size calculation, 156 - DK4_E_MEMORY_ALLOCATION_FAILED<br> 157 with mem.elsize and mem.nelem set if there is not enough memory 158 available. 159*/ 160wchar_t * 161dk4pathw_dup_change_suffix( 162 wchar_t const *srcname, 163 wchar_t const *newsu, 164 dk4_er_t *erp 165); 166 167/** Calculate buffer size required to concatenate a directory 168 name and a file name. 169 @param dirname Directory name. 170 @param filename File name. 171 @param erp Error report, may be NULL. 172 @return Buffer size for concatenated names including the finalizer 173 byte on success, 0 on error. 174 175 Error codes: 176 - DK4_E_INVALID_ARGUMENTS<br> 177 if dirname or filename is NULL, 178 - DK4_E_MATH_OVERFLOW<br> 179 if the calculation results in a numeric overflow. 180*/ 181size_t 182dk4pathw_concatenate_size( 183 wchar_t const *dirname, 184 wchar_t const *filename, 185 dk4_er_t *erp 186); 187 188/** Concatenate a directory name and a file name into an existing buffer. 189 This function simply concatenates directory name and file name, 190 . and .. for current and parent directory are not resolved. 191 @param buffer Result buffer for combined file name. 192 @param szbuf Buffer size (number of wchar_t). 193 @param dirn Directory name. 194 @param filen File name. 195 @param erp Error report, may be NULL. 196 @return 1 on success, 0 on error. 197 198 Error codes: 199 - DK4_E_INVALID_ARGUMENTS<br> 200 if buffer, dirn or filen is NULL or szbuf is 0, 201 - DK4_E_BUFFER_TOO_SMALL<br> 202 if the buffer is too small. 203*/ 204int 205dk4pathw_concatenate_buffer( 206 wchar_t *buffer, 207 size_t szbuf, 208 wchar_t const *dirn, 209 wchar_t const *filen, 210 dk4_er_t *erp 211); 212 213/** Concatenate a directory name and a file name into newly 214 allocated memory. 215 This function simply concatenates directory name and file name, 216 . and .. for current and parent directory are not resolved. 217 @param dirn Directory name. 218 @param filen File name. 219 @param erp Error report, may be NULL. 220 @return Valid pointer to newly allocated memory containing the 221 concatenation on success, NULL on error. 222 On success use dk4mem_free() to release the memory when done 223 with it. 224 225 Error codes: 226 - DK4_E_INVALID_ARGUMENTS<br> 227 if dirn or filen is NULL, 228 - DK4_E_MATH_OVERFLOW<br> 229 if the size calculation results in a numeric overflow. 230 - DK4_E_MEMORY_ALLOCATION_FAILED<br> 231 if the memory allocation failed, 232 - DK4_E_BUFFER_TOO_SMALL<br> 233 if the buffer is too small. 234*/ 235wchar_t * 236dk4pathw_concatenate_new( 237 wchar_t const *dirn, 238 wchar_t const *filen, 239 dk4_er_t *erp 240); 241 242#ifdef __cplusplus 243} 244#endif 245 246 247 248%% module 249 250 251#include "dk4conf.h" 252#include <libdk4c/dk4pathw.h> 253#include <libdk4base/dk4mem.h> 254#include <libdk4base/dk4strw.h> 255#include <libdk4ma/dk4maasz.h> 256 257 258#if DK4_HAVE_STRING_H 259#ifndef STRING_H_INCLUDED 260#include <string.h> 261#define STRING_H_INCLUDED 1 262#endif 263#endif 264 265#if DK4_HAVE_WCHAR_H 266#ifndef WCHAR_H_INCLUDED 267#include <wchar.h> 268#define WCHAR_H_INCLUDED 1 269#endif 270#endif 271 272#if DK4_HAVE_ASSERT_H 273#ifndef ASSERT_H_INCLUDED 274#include <assert.h> 275#define ASSERT_H_INCLUDED 1 276#endif 277#endif 278 279#include <libdk4base/dk4unused.h> 280 281 282 283$!trace-include 284 285 286 287/** Constant keywords used by the dk4pathw module. 288*/ 289static const wchar_t * const dk4pathw_kw[] = { 290#if DK4_HAVE_BACKSLASH_AS_SEP 291/* 0 */ L"\\", 292/* 1 */ L"/", 293#else 294/* 0 */ L"/", 295/* 1 */ L"\\", 296#endif 297/* 2 */ L".", 298/* 3 */ L"..", 299/* 4 */ L":" 300}; 301 302 303 304/** Get last character from a string. 305 @param str String to test. 306 @return Last character from string. 307*/ 308static 309wchar_t 310dk4pathw_lastchar(const wchar_t *str) 311{ 312 wchar_t back = '\0'; 313#if DK4_USE_ASSERT 314 assert(NULL != str); 315#endif 316 if (str) { while (L'\0' != *str) { back = *(str++); } } 317 return back; 318} 319 320 321 322int 323dk4pathw_is_absolute(const wchar_t *path) 324{ 325 int back = 0; 326#if DK4_ON_WINDOWS 327 wchar_t c; 328#endif 329#if DK4_USE_ASSERT 330 assert(NULL != path); 331#endif 332 if (NULL != path) { 333#if DK4_ON_WINDOWS 334 c = *path; 335 if ( *(dk4pathw_kw[0]) == c ) { 336 back = 1; 337 } else { 338 if (((L'a' <= c) && (L'z' >= c)) || ((L'A' <= c) && (L'Z' >= c))) { 339 if ( *(dk4pathw_kw[4]) == path[1] ) { 340 c = path[2]; 341 if ( ( *(dk4pathw_kw[0]) == c ) || ( L'\0' == c ) ) { 342 back = 1; 343 } 344 } 345 } 346 } 347#else 348 if ( *(dk4pathw_kw[0]) == *path ) { 349 back = 1; 350 } 351#endif 352 } 353 return back; 354} 355 356 357 358int 359dk4pathw_is_relative(const wchar_t *path) 360{ 361 int back = 0; 362#if DK4_USE_ASSERT 363 assert(NULL != path); 364#endif 365 if (NULL != path) { 366 if (NULL != dk4strw_chr(path, *(dk4pathw_kw[0]))) { 367 if (0 == dk4pathw_is_absolute(path)) { 368 back = 1; 369 } 370 } 371 } 372 return back; 373} 374 375 376 377int 378dk4pathw_append( 379 wchar_t *buffer, size_t sz, const wchar_t *filename, dk4_er_t *erp 380) 381{ 382 wchar_t *pc; /* Current path part to append */ 383 wchar_t *pn; /* Next part to append */ 384 wchar_t *pd; /* Part to delete */ 385 wchar_t *mycp = NULL; /* Private copy */ 386#if DK4_ON_WINDOWS 387 int ischr; 388#endif 389 int back = 0; /* Function result */ 390 int failed = 0; /* Definitely failed */ 391 wchar_t lch; /* Last character from string */ 392 393#if DK4_USE_ASSERT 394 assert(NULL != buffer); 395 assert(0 < sz); 396 assert(NULL != filename); 397#endif 398 if ((NULL != buffer) && (NULL != filename) && (0 < sz)) { 399 mycp = dk4strw_dup(filename, erp); 400 if (NULL != mycp) { 401 pc = mycp; 402 while ((NULL != pc) && (0 == failed)) { 403 pn = dk4strw_chr(pc, *(dk4pathw_kw[0])); 404 if (NULL != pn) { *(pn++) = L'\0'; } 405 if (0 != dk4strw_cmp(pc, dk4pathw_kw[2])) { 406 if (0 == dk4strw_cmp(pc, dk4pathw_kw[3])) { /* Remove from buffer */ 407 pd = dk4strw_rchr(buffer, *(dk4pathw_kw[0])); 408 if (NULL != pd) { 409 *pd = L'\0'; 410 if (0 < dk4strw_len(&(pd[1]))) { 411#if DK4_ON_WINDOWS 412 if (2 == dk4strw_len(buffer)) { 413 ischr = 0; 414 if ((L'A' <= buffer[0]) && (L'Z' >= buffer[0])) { 415 ischr = 1; 416 } 417 if ((L'a' <= buffer[0]) && (L'z' >= buffer[0])) { 418 ischr = 1; 419 } 420 if (1 == ischr) { 421 if (L':' == buffer[1]) { 422 buffer[2] = *(dk4pathw_kw[0]); 423 buffer[3] = L'\0'; 424 } 425 } 426 } 427#else 428 if (0 == dk4strw_len(buffer)) { 429 *pd = *(dk4pathw_kw[0]); 430 pd++; 431 *pd = L'\0'; 432 } 433#endif 434 } else { 435 failed = 1; 436 dk4error_set_simple_error_code(erp, DK4_E_SYNTAX); 437 } 438 } else { 439 failed = 1; 440 dk4error_set_simple_error_code(erp, DK4_E_SYNTAX); 441 } 442 } else { /* Add to buffer */ 443 lch = dk4pathw_lastchar(buffer); 444 if (lch != *(dk4pathw_kw[0])) { 445 if (0 != dk4strw_cat_s(buffer, sz, dk4pathw_kw[0], erp)) { 446 if (0 == dk4strw_cat_s(buffer, sz, pc, erp)) { 447 failed = 1; 448 } 449 } else { 450 failed = 1; 451 } 452 } else { 453 if (0 == dk4strw_cat_s(buffer, sz, pc, erp)) { 454 failed = 1; 455 } 456 } 457 } 458 } 459 pc = pn; 460 } 461 if (0 == failed) { 462 back = 1; 463 } 464 dk4mem_release(mycp); 465 } 466 } else { 467 dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); 468 } 469 return back; 470} 471 472 473 474wchar_t * 475dk4pathw_get_suffix(const wchar_t *filename, dk4_er_t *erp) 476{ 477 const wchar_t *back = NULL; 478#if DK4_USE_ASSERT 479 assert(NULL != filename); 480#endif 481 if (NULL != filename) { 482 while (L'\0' != *filename) { 483 if ((L'\\' == *filename) || (L'/' == *filename)) { 484 back = NULL; 485 } else { 486 if (L'.' == *filename) { 487 back = filename; 488 } 489 } 490 filename++; 491 } 492 } else { 493 dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); 494 } 495 return ((wchar_t *)back); 496} 497 498 499 500void 501dk4pathw_correct_sep(wchar_t *filename) 502{ 503#if DK4_USE_ASSERT 504 assert(NULL != filename); 505#endif 506 if (NULL != filename) { 507 while (L'\0' != *filename) { 508#if DK4_HAVE_BACKSLASH_AS_SEP 509 if (L'/' == *filename) { *filename = L'\\'; } 510#else 511 if (L'\\' == *filename) { *filename = L'/'; } 512#endif 513 filename++; 514 } 515 } 516} 517 518 519 520int 521dk4pathw_must_expand( 522#if DK4_ON_WINDOWS 523 const wchar_t *filename 524#else 525 const wchar_t * DK4_ARG_UNUSED(filename) 526#endif 527) 528{ 529#if DK4_ON_WINDOWS 530 int back = 0; 531#if DK4_USE_ASSERT 532 assert(NULL != filename); 533#endif 534 if (NULL != filename) { 535 if (L'\\' == filename[0]) { 536 if (L'\\' == filename[1]) { 537 if (L'?' == filename[2]) { 538 if (L'\\' == filename[3]) { 539 filename = &(filename[4]); 540 } 541 } 542 } 543 } 544 while((L'\0' != *filename) && (0 == back)) { 545 if (L'*' == *filename) { 546 back = 1; 547 } else { 548 if (L'?' == *filename) { 549 back = 1; 550 } else { 551 filename++; 552 } 553 } 554 } 555 } 556 return back; 557#else 558 DK4_UNUSED_ARG(filename) 559 return 0; 560#endif 561} 562 563 564 565int 566dk4pathw_set_suffix( 567 wchar_t *pdst, 568 size_t szdst, 569 wchar_t const *srcname, 570 wchar_t const *newsu, 571 dk4_er_t *erp 572) 573{ 574 wchar_t *sp = NULL; 575 int back = 0; 576 577#if DK4_USE_ASSERT 578 assert(NULL != pdst); 579 assert(0 < szdst); 580 assert(NULL != srcname); 581 assert(NULL != newsu); 582#endif 583 if ((NULL == pdst) || (NULL == srcname) || (NULL == newsu) || (0 == szdst)) { 584 dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); 585 goto finished; 586 } 587 if (0 == dk4strw_cpy_s(pdst, szdst, srcname, erp)) { 588 goto finished; 589 } 590 sp = dk4pathw_get_suffix(pdst, NULL); 591 if (NULL != sp) { 592 *sp = L'\0'; 593 } 594 back = dk4strw_cat_s(pdst, szdst, newsu, erp); 595 596 finished: 597 return back; 598} 599 600 601 602wchar_t * 603dk4pathw_dup_change_suffix( 604 wchar_t const *srcname, 605 wchar_t const *newsu, 606 dk4_er_t *erp 607) 608{ 609 dk4_er_t er; 610 wchar_t *sp; 611 wchar_t *back = NULL; 612 size_t lold; 613 size_t lsuff; 614 size_t lnew; 615#if 0 616 size_t i; 617#endif 618 619#if DK4_USE_ASSERT 620 assert(NULL != srcname); 621 assert(NULL != newsu); 622#endif 623 if ((NULL == srcname) || (NULL == newsu)) { 624 dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); 625 goto finished; 626 } 627 sp = dk4pathw_get_suffix(srcname, NULL); 628 lold = dk4strw_len(srcname); 629 lsuff = 0; 630 if (NULL != sp) { lsuff = dk4strw_len(sp); } 631 if (lsuff > lold) { 632 /* BUG */ 633 dk4error_set_simple_error_code(erp, DK4_E_BUG); 634 goto finished; 635 } 636 lold -= lsuff; 637 lsuff = dk4strw_len(newsu); 638 dk4error_init(&er); 639 lnew = dk4ma_size_t_add(lold, lsuff, &er); 640 lnew = dk4ma_size_t_add(lnew, 1, &er); 641 if (DK4_E_NONE != er.ec) { 642 dk4error_copy(erp, &er); 643 goto finished; 644 } 645 back = dk4mem_new(wchar_t,lnew,erp); 646 if (NULL == back) { 647 goto finished; 648 } 649#if 0 650 /* 2018-04-04 Bugfix 651 The new file name may be shorter than the original 652 file name, so we must not copy the original name 653 into the new buffer! 654 */ 655 dk4strw_cpy_s(back, lnew, srcname, NULL); 656 sp = dk4pathw_get_suffix(back, NULL); 657 if (NULL != sp) { *sp = L'\0'; } 658#endif 659#if 0 660 for (i = 0; i < lold; i++) { 661 back[i] = srcname[i]; 662 } 663#endif 664 DK4_MEMCPY(back,srcname,(lold*sizeof(wchar_t))); 665 back[lold] = L'\0'; 666 dk4strw_cat_s(back, lnew, newsu, NULL); 667 668 finished: 669 return back; 670} 671 672 673 674size_t 675dk4pathw_concatenate_size( 676 wchar_t const *dirname, 677 wchar_t const *filename, 678 dk4_er_t *erp 679) 680{ 681 dk4_er_t er; 682 size_t back = 0; 683 size_t sldir = 0; 684 size_t slfil = 0; 685 size_t toadd = 2; 686 687#if DK4_USE_ASSERT 688 assert(NULL != dirname); 689 assert(NULL != filename); 690#endif 691 if ((NULL != dirname) && (NULL != filename)) { 692 sldir = dk4strw_len(dirname); 693 slfil = dk4strw_len(filename); 694 if (0 < sldir) { 695 if (*(dk4pathw_kw[0]) == dirname[sldir - 1]) { toadd = 1; } 696 } 697 dk4error_init(&er); 698 back = dk4ma_size_t_add( dk4ma_size_t_add(sldir, slfil, &er), toadd, &er ); 699 if (DK4_E_NONE != er.ec) { 700 dk4error_copy(erp, &er); 701 back = 0; 702 } 703 } 704 else { 705 dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); 706 } 707 return back; 708} 709 710 711 712int 713dk4pathw_concatenate_buffer( 714 wchar_t *buffer, 715 size_t szbuf, 716 wchar_t const *dirn, 717 wchar_t const *filen, 718 dk4_er_t *erp 719) 720{ 721 size_t sld; 722 int back = 0; 723 int sep = 1; 724#if DK4_USE_ASSERT 725 assert(NULL != buffer); 726 assert(0 < szbuf); 727 assert(NULL != dirn); 728 assert(NULL != filen); 729#endif 730 if ((NULL != buffer) && (NULL != dirn) && (NULL != filen) && (0 != szbuf)) { 731 sld = dk4strw_len(dirn); 732 if (0 < sld) { if (dirn[sld - 1] == *(dk4pathw_kw[0])) { sep = 0; } } 733 back = dk4strw_cpy_s(buffer, szbuf, dirn, erp); 734 if (0 != back) { 735 if (0 != sep) { 736 back = dk4strw_cat_s(buffer, szbuf, dk4pathw_kw[0], erp); 737 } 738 } 739 if (0 != back) { 740 back = dk4strw_cat_s(buffer, szbuf, filen, erp); 741 } 742 } 743 else { 744 dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); 745 } 746 if ((0 == back) && (NULL != buffer) && (0 < szbuf)) { buffer[0] = L'\0'; } 747 return back; 748} 749 750 751 752wchar_t * 753dk4pathw_concatenate_new( 754 wchar_t const *dirn, 755 wchar_t const *filen, 756 dk4_er_t *erp 757) 758{ 759 wchar_t *back = NULL; 760 size_t bl = 0; 761#if DK4_USE_ASSERT 762 assert(NULL != dirn); 763 assert(NULL != filen); 764#endif 765 if ((NULL != dirn) && (NULL != filen)) { 766 bl = dk4pathw_concatenate_size(dirn, filen, erp); 767 if (0 != bl) { 768 back = dk4mem_new(wchar_t,bl,erp); 769 if (NULL != back) { 770 if (0 == dk4pathw_concatenate_buffer(back, bl, dirn, filen, erp)) { 771 dk4mem_free(back); 772 back = NULL; 773 } 774 } 775 } 776 } 777 else { 778 dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); 779 } 780 return back; 781} 782 783 784