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