1 /***********************************************************************************************************************************
2 String Handler
3 ***********************************************************************************************************************************/
4 #include "build.auto.h"
5 
6 #include <ctype.h>
7 #include <stdarg.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 
12 #include "common/debug.h"
13 #include "common/macro.h"
14 #include "common/memContext.h"
15 #include "common/type/string.h"
16 #include "common/type/stringList.h"
17 #include "common/type/stringZ.h"
18 
19 /***********************************************************************************************************************************
20 Constant strings that are generally useful
21 ***********************************************************************************************************************************/
22 STRING_EXTERN(BRACKETL_STR,                                         BRACKETL_Z);
23 STRING_EXTERN(BRACKETR_STR,                                         BRACKETR_Z);
24 STRING_EXTERN(COLON_STR,                                            COLON_Z);
25 STRING_EXTERN(CR_STR,                                               CR_Z);
26 STRING_EXTERN(CRLF_STR,                                             CRLF_Z);
27 STRING_EXTERN(DASH_STR,                                             DASH_Z);
28 STRING_EXTERN(DOT_STR,                                              DOT_Z);
29 STRING_EXTERN(DOTDOT_STR,                                           DOTDOT_Z);
30 STRING_EXTERN(EMPTY_STR,                                            EMPTY_Z);
31 STRING_EXTERN(EQ_STR,                                               EQ_Z);
32 STRING_EXTERN(FALSE_STR,                                            FALSE_Z);
33 STRING_EXTERN(FSLASH_STR,                                           FSLASH_Z);
34 STRING_EXTERN(LF_STR,                                               LF_Z);
35 STRING_EXTERN(N_STR,                                                N_Z);
36 STRING_EXTERN(NULL_STR,                                             NULL_Z);
37 STRING_EXTERN(QUOTED_STR,                                           QUOTED_Z);
38 STRING_EXTERN(TRUE_STR,                                             TRUE_Z);
39 STRING_EXTERN(Y_STR,                                                Y_Z);
40 STRING_EXTERN(ZERO_STR,                                             ZERO_Z);
41 
42 /***********************************************************************************************************************************
43 Maximum size of a string
44 ***********************************************************************************************************************************/
45 #define STRING_SIZE_MAX                                            1073741824
46 
47 #define CHECK_SIZE(size)                                                                                                           \
48     do                                                                                                                             \
49     {                                                                                                                              \
50         if ((size) > STRING_SIZE_MAX)                                                                                              \
51             THROW(AssertError, "string size must be <= " STRINGIFY(STRING_SIZE_MAX) " bytes");                                     \
52     }                                                                                                                              \
53     while (0)
54 
55 /***********************************************************************************************************************************
56 Object type
57 ***********************************************************************************************************************************/
58 struct String
59 {
60     StringPub pub;                                                  // Publicly accessible variables
61     MemContext *memContext;                                         // Required for dynamically allocated strings
62 };
63 
64 /**********************************************************************************************************************************/
65 String *
strNew(void)66 strNew(void)
67 {
68     FUNCTION_TEST_VOID();
69 
70     // Create object
71     String *this = memNew(sizeof(String));
72 
73     *this = (String)
74     {
75         .pub =
76         {
77             // A zero-length string is not very useful so assume this string is being created for appending and allocate extra space
78             .extra = STRING_EXTRA_MIN,
79         },
80         .memContext = memContextCurrent(),
81     };
82 
83     // Allocate and assign string
84     this->pub.buffer = memNew(STRING_EXTRA_MIN + 1);
85     this->pub.buffer[0] = '\0';
86 
87     FUNCTION_TEST_RETURN(this);
88 }
89 
90 /**********************************************************************************************************************************/
91 String *
strNewZ(const char * const string)92 strNewZ(const char *const string)
93 {
94     FUNCTION_TEST_BEGIN();
95         FUNCTION_TEST_PARAM(STRINGZ, string);
96     FUNCTION_TEST_END();
97 
98     ASSERT(string != NULL);
99 
100     // Check size
101     size_t stringSize = strlen(string);
102     CHECK_SIZE(stringSize);
103 
104     // Create object
105     String *this = memNew(sizeof(String));
106 
107     *this = (String)
108     {
109         .pub =
110         {
111             .size = (unsigned int)stringSize,
112         },
113         .memContext = memContextCurrent(),
114     };
115 
116     // Allocate and assign string
117     this->pub.buffer = memNew(strSize(this) + this->pub.extra + 1);
118     strcpy(this->pub.buffer, string);
119 
120     FUNCTION_TEST_RETURN(this);
121 }
122 
123 /**********************************************************************************************************************************/
strNewDbl(double value)124 String *strNewDbl(double value)
125 {
126     FUNCTION_TEST_BEGIN();
127         FUNCTION_TEST_PARAM(DOUBLE, value);
128     FUNCTION_TEST_END();
129 
130     char working[CVT_BASE10_BUFFER_SIZE];
131 
132     cvtDoubleToZ(value, working, sizeof(working));
133 
134     FUNCTION_TEST_RETURN(strNewZ(working));
135 }
136 
137 /**********************************************************************************************************************************/
138 String *
strNewBuf(const Buffer * buffer)139 strNewBuf(const Buffer *buffer)
140 {
141     FUNCTION_TEST_BEGIN();
142         FUNCTION_TEST_PARAM(BUFFER, buffer);
143     FUNCTION_TEST_END();
144 
145     ASSERT(buffer != NULL);
146 
147     // Check size
148     CHECK_SIZE(bufUsed(buffer));
149 
150     // Create object
151     String *this = memNew(sizeof(String));
152 
153     *this = (String)
154     {
155         .pub =
156         {
157             .size = (unsigned int)bufUsed(buffer),
158         },
159         .memContext = memContextCurrent(),
160     };
161 
162     // Allocate and assign string
163     this->pub.buffer = memNew(strSize(this) + 1);
164     memcpy(this->pub.buffer, bufPtrConst(buffer), strSize(this));
165     this->pub.buffer[strSize(this)] = 0;
166 
167     FUNCTION_TEST_RETURN(this);
168 }
169 
170 /**********************************************************************************************************************************/
171 String *
strNewEncode(EncodeType type,const Buffer * buffer)172 strNewEncode(EncodeType type, const Buffer *buffer)
173 {
174     FUNCTION_TEST_BEGIN();
175         FUNCTION_TEST_PARAM(ENUM, type);
176         FUNCTION_TEST_PARAM(BUFFER, buffer);
177     FUNCTION_TEST_END();
178 
179     ASSERT(buffer != NULL);
180 
181     // Check encoded size
182     size_t size = encodeToStrSize(type, bufUsed(buffer));
183     CHECK_SIZE(size);
184 
185     // Create object
186     String *this = memNew(sizeof(String));
187 
188     *this = (String)
189     {
190         .pub =
191         {
192             .size = (unsigned int)size,
193         },
194         .memContext = memContextCurrent(),
195     };
196 
197     // Allocate and encode buffer
198     this->pub.buffer = memNew(strSize(this) + 1);
199     encodeToStr(type, bufPtrConst(buffer), bufUsed(buffer), this->pub.buffer);
200 
201     FUNCTION_TEST_RETURN(this);
202 }
203 
204 /**********************************************************************************************************************************/
205 String *
strNewFmt(const char * format,...)206 strNewFmt(const char *format, ...)
207 {
208     FUNCTION_TEST_BEGIN();
209         FUNCTION_TEST_PARAM(STRINGZ, format);
210     FUNCTION_TEST_END();
211 
212     ASSERT(format != NULL);
213 
214     // Create object
215     String *this = memNew(sizeof(String));
216 
217     *this = (String)
218     {
219         .memContext = memContextCurrent(),
220     };
221 
222     // Determine how long the allocated string needs to be
223     va_list argumentList;
224     va_start(argumentList, format);
225     size_t formatSize = (size_t)vsnprintf(NULL, 0, format, argumentList);
226     va_end(argumentList);
227 
228     // Check size
229     CHECK_SIZE(formatSize);
230 
231     // Allocate and assign string
232     this->pub.size = (unsigned int)formatSize;
233     this->pub.buffer = memNew(strSize(this) + 1);
234     va_start(argumentList, format);
235     vsnprintf(this->pub.buffer, strSize(this) + 1, format, argumentList);
236     va_end(argumentList);
237 
238     FUNCTION_TEST_RETURN(this);
239 }
240 
241 /**********************************************************************************************************************************/
242 String *
strNewN(const char * string,size_t size)243 strNewN(const char *string, size_t size)
244 {
245     FUNCTION_TEST_BEGIN();
246         FUNCTION_TEST_PARAM_P(CHARDATA, string);
247         FUNCTION_TEST_PARAM(SIZE, size);
248     FUNCTION_TEST_END();
249 
250     ASSERT(string != NULL);
251 
252     // Check size
253     CHECK_SIZE(size);
254 
255     // Create object
256     String *this = memNew(sizeof(String));
257 
258     *this = (String)
259     {
260         .pub =
261         {
262             .size = (unsigned int)size,
263         },
264         .memContext = memContextCurrent(),
265     };
266 
267     // Allocate and assign string
268     this->pub.buffer = memNew(strSize(this) + 1);
269     strncpy(this->pub.buffer, string, strSize(this));
270     this->pub.buffer[strSize(this)] = 0;
271 
272     // Return buffer
273     FUNCTION_TEST_RETURN(this);
274 }
275 
276 /**********************************************************************************************************************************/
277 String *
strBase(const String * this)278 strBase(const String *this)
279 {
280     FUNCTION_TEST_BEGIN();
281         FUNCTION_TEST_PARAM(STRING, this);
282     FUNCTION_TEST_END();
283 
284     ASSERT(this != NULL);
285 
286     FUNCTION_TEST_RETURN(strNewZ(strBaseZ(this)));
287 }
288 
289 const char *
strBaseZ(const String * this)290 strBaseZ(const String *this)
291 {
292     FUNCTION_TEST_BEGIN();
293         FUNCTION_TEST_PARAM(STRING, this);
294     FUNCTION_TEST_END();
295 
296     ASSERT(this != NULL);
297 
298     const char *end = this->pub.buffer + strSize(this);
299 
300     while (end > this->pub.buffer && *(end - 1) != '/')
301         end--;
302 
303     FUNCTION_TEST_RETURN(end);
304 }
305 
306 /**********************************************************************************************************************************/
307 bool
strBeginsWith(const String * this,const String * beginsWith)308 strBeginsWith(const String *this, const String *beginsWith)
309 {
310     FUNCTION_TEST_BEGIN();
311         FUNCTION_TEST_PARAM(STRING, this);
312         FUNCTION_TEST_PARAM(STRING, beginsWith);
313     FUNCTION_TEST_END();
314 
315     ASSERT(this != NULL);
316     ASSERT(beginsWith != NULL);
317 
318     FUNCTION_TEST_RETURN(strBeginsWithZ(this, strZ(beginsWith)));
319 }
320 
321 bool
strBeginsWithZ(const String * this,const char * beginsWith)322 strBeginsWithZ(const String *this, const char *beginsWith)
323 {
324     FUNCTION_TEST_BEGIN();
325         FUNCTION_TEST_PARAM(STRING, this);
326         FUNCTION_TEST_PARAM(STRINGZ, beginsWith);
327     FUNCTION_TEST_END();
328 
329     ASSERT(this != NULL);
330     ASSERT(beginsWith != NULL);
331 
332     bool result = false;
333     unsigned int beginsWithSize = (unsigned int)strlen(beginsWith);
334 
335     if (strSize(this) >= beginsWithSize)
336         result = strncmp(strZ(this), beginsWith, beginsWithSize) == 0;
337 
338     FUNCTION_TEST_RETURN(result);
339 }
340 
341 /***********************************************************************************************************************************
342 Resize the string to allow the requested number of characters to be appended
343 ***********************************************************************************************************************************/
344 static void
strResize(String * this,size_t requested)345 strResize(String *this, size_t requested)
346 {
347     FUNCTION_TEST_BEGIN();
348         FUNCTION_TEST_PARAM(STRING, this);
349         FUNCTION_TEST_PARAM(SIZE, requested);
350     FUNCTION_TEST_END();
351 
352     if (requested > this->pub.extra)
353     {
354         // Check size
355         CHECK_SIZE(strSize(this) + requested);
356 
357         // Calculate new extra needs to satisfy request and leave extra space for new growth
358         this->pub.extra = (unsigned int)(requested + ((strSize(this) + requested) / 2));
359 
360         // Adding too little extra space usually leads to immediate resizing so enforce a minimum
361         if (this->pub.extra < STRING_EXTRA_MIN)
362             this->pub.extra = STRING_EXTRA_MIN;
363 
364         MEM_CONTEXT_BEGIN(this->memContext)
365         {
366             this->pub.buffer = memResize(this->pub.buffer, strSize(this) + this->pub.extra + 1);
367         }
368         MEM_CONTEXT_END();
369     }
370 
371     FUNCTION_TEST_RETURN_VOID();
372 }
373 
374 /**********************************************************************************************************************************/
375 String *
strCat(String * this,const String * cat)376 strCat(String *this, const String *cat)
377 {
378     FUNCTION_TEST_BEGIN();
379         FUNCTION_TEST_PARAM(STRING, this);
380         FUNCTION_TEST_PARAM(STRING, cat);
381     FUNCTION_TEST_END();
382 
383     ASSERT(this != NULL);
384     ASSERT(cat != NULL);
385 
386     FUNCTION_TEST_RETURN(strCatZN(this, strZ(cat), strSize(cat)));
387 }
388 
389 String *
strCatZ(String * this,const char * cat)390 strCatZ(String *this, const char *cat)
391 {
392     FUNCTION_TEST_BEGIN();
393         FUNCTION_TEST_PARAM(STRING, this);
394         FUNCTION_TEST_PARAM(STRINGZ, cat);
395     FUNCTION_TEST_END();
396 
397     ASSERT(this != NULL);
398     ASSERT(cat != NULL);
399 
400     // Determine length of string to append
401     size_t sizeGrow = strlen(cat);
402 
403     // Ensure there is enough space to grow the string
404     strResize(this, sizeGrow);
405 
406     // Append the string
407     strcpy(this->pub.buffer + strSize(this), cat);
408     this->pub.size += (unsigned int)sizeGrow;
409     this->pub.extra -= (unsigned int)sizeGrow;
410 
411     FUNCTION_TEST_RETURN(this);
412 }
413 
414 String *
strCatZN(String * this,const char * cat,size_t size)415 strCatZN(String *this, const char *cat, size_t size)
416 {
417     FUNCTION_TEST_BEGIN();
418         FUNCTION_TEST_PARAM(STRING, this);
419         FUNCTION_TEST_PARAM(STRINGZ, cat);
420         FUNCTION_TEST_PARAM(SIZE, size);
421     FUNCTION_TEST_END();
422 
423     ASSERT(this != NULL);
424     ASSERT(cat != NULL);
425 
426     // Ensure there is enough space to grow the string
427     strResize(this, size);
428 
429     // Append the string
430     strncpy(this->pub.buffer + strSize(this), cat, size);
431     this->pub.buffer[strSize(this) + size] = '\0';
432 
433     // Update size/extra
434     this->pub.size += (unsigned int)size;
435     this->pub.extra -= (unsigned int)size;
436 
437     FUNCTION_TEST_RETURN(this);
438 }
439 
440 /**********************************************************************************************************************************/
441 String *
strCatChr(String * this,char cat)442 strCatChr(String *this, char cat)
443 {
444     FUNCTION_TEST_BEGIN();
445         FUNCTION_TEST_PARAM(STRING, this);
446         FUNCTION_TEST_PARAM(CHAR, cat);
447     FUNCTION_TEST_END();
448 
449     ASSERT(this != NULL);
450     ASSERT(cat != 0);
451 
452     // Ensure there is enough space to grow the string
453     strResize(this, 1);
454 
455     // Append the character
456     this->pub.buffer[this->pub.size++] = cat;
457     this->pub.buffer[this->pub.size] = 0;
458     this->pub.extra--;
459 
460     FUNCTION_TEST_RETURN(this);
461 }
462 
463 /**********************************************************************************************************************************/
464 String *
strCatEncode(String * this,EncodeType type,const Buffer * buffer)465 strCatEncode(String *this, EncodeType type, const Buffer *buffer)
466 {
467     FUNCTION_TEST_BEGIN();
468         FUNCTION_TEST_PARAM(STRING, this);
469         FUNCTION_TEST_PARAM(ENUM, type);
470         FUNCTION_TEST_PARAM(BUFFER, buffer);
471     FUNCTION_TEST_END();
472 
473     ASSERT(this != NULL);
474     ASSERT(buffer != NULL);
475 
476     // Ensure there is enough space to grow the string
477     size_t encodeSize = encodeToStrSize(type, bufUsed(buffer));
478     strResize(this, encodeSize);
479 
480     // Append the encoded string
481     encodeToStr(type, bufPtrConst(buffer), bufUsed(buffer), this->pub.buffer + strSize(this));
482 
483     // Update size/extra
484     this->pub.size += (unsigned int)encodeSize;
485     this->pub.extra -= (unsigned int)encodeSize;
486 
487     FUNCTION_TEST_RETURN(this);
488 }
489 
490 /**********************************************************************************************************************************/
491 String *
strCatFmt(String * this,const char * format,...)492 strCatFmt(String *this, const char *format, ...)
493 {
494     FUNCTION_TEST_BEGIN();
495         FUNCTION_TEST_PARAM(STRING, this);
496         FUNCTION_TEST_PARAM(STRINGZ, format);
497     FUNCTION_TEST_END();
498 
499     ASSERT(this != NULL);
500     ASSERT(format != NULL);
501 
502     // Determine how long the allocated string needs to be
503     va_list argumentList;
504     va_start(argumentList, format);
505     size_t sizeGrow = (size_t)vsnprintf(NULL, 0, format, argumentList);
506     va_end(argumentList);
507 
508     // Ensure there is enough space to grow the string
509     strResize(this, sizeGrow);
510 
511     // Append the formatted string
512     va_start(argumentList, format);
513     vsnprintf(this->pub.buffer + strSize(this), sizeGrow + 1, format, argumentList);
514     va_end(argumentList);
515 
516     this->pub.size += (unsigned int)sizeGrow;
517     this->pub.extra -= (unsigned int)sizeGrow;
518 
519     FUNCTION_TEST_RETURN(this);
520 }
521 
522 /**********************************************************************************************************************************/
523 int
strCmp(const String * this,const String * compare)524 strCmp(const String *this, const String *compare)
525 {
526     FUNCTION_TEST_BEGIN();
527         FUNCTION_TEST_PARAM(STRING, this);
528         FUNCTION_TEST_PARAM(STRING, compare);
529     FUNCTION_TEST_END();
530 
531     if (this != NULL && compare != NULL)
532         FUNCTION_TEST_RETURN(strcmp(strZ(this), strZ(compare)));
533     else if (this == NULL)
534     {
535         if (compare == NULL)
536             FUNCTION_TEST_RETURN(0);
537 
538         FUNCTION_TEST_RETURN(-1);
539     }
540 
541     FUNCTION_TEST_RETURN(1);
542 }
543 
544 int
strCmpZ(const String * this,const char * compare)545 strCmpZ(const String *this, const char *compare)
546 {
547     FUNCTION_TEST_BEGIN();
548         FUNCTION_TEST_PARAM(STRING, this);
549         FUNCTION_TEST_PARAM(STRINGZ, compare);
550     FUNCTION_TEST_END();
551 
552     FUNCTION_TEST_RETURN(strCmp(this, compare == NULL ? NULL : STRDEF(compare)));
553 }
554 
555 /**********************************************************************************************************************************/
556 String *
strDup(const String * this)557 strDup(const String *this)
558 {
559     FUNCTION_TEST_BEGIN();
560         FUNCTION_TEST_PARAM(STRING, this);
561     FUNCTION_TEST_END();
562 
563     String *result = NULL;
564 
565     if (this != NULL)
566         result = strNewZ(strZ(this));
567 
568     FUNCTION_TEST_RETURN(result);
569 }
570 
571 /**********************************************************************************************************************************/
572 bool
strEmpty(const String * this)573 strEmpty(const String *this)
574 {
575     FUNCTION_TEST_BEGIN();
576         FUNCTION_TEST_PARAM(STRING, this);
577     FUNCTION_TEST_END();
578 
579     FUNCTION_TEST_RETURN(strSize(this) == 0);
580 }
581 
582 /**********************************************************************************************************************************/
583 bool
strEndsWith(const String * this,const String * endsWith)584 strEndsWith(const String *this, const String *endsWith)
585 {
586     FUNCTION_TEST_BEGIN();
587         FUNCTION_TEST_PARAM(STRING, this);
588         FUNCTION_TEST_PARAM(STRING, endsWith);
589     FUNCTION_TEST_END();
590 
591     ASSERT(this != NULL);
592     ASSERT(endsWith != NULL);
593 
594     FUNCTION_TEST_RETURN(strEndsWithZ(this, strZ(endsWith)));
595 }
596 
597 bool
strEndsWithZ(const String * this,const char * endsWith)598 strEndsWithZ(const String *this, const char *endsWith)
599 {
600     FUNCTION_TEST_BEGIN();
601         FUNCTION_TEST_PARAM(STRING, this);
602         FUNCTION_TEST_PARAM(STRINGZ, endsWith);
603     FUNCTION_TEST_END();
604 
605     ASSERT(this != NULL);
606     ASSERT(endsWith != NULL);
607 
608     bool result = false;
609     unsigned int endsWithSize = (unsigned int)strlen(endsWith);
610 
611     if (strSize(this) >= endsWithSize)
612         result = strcmp(strZ(this) + (strSize(this) - endsWithSize), endsWith) == 0;
613 
614     FUNCTION_TEST_RETURN(result);
615 }
616 
617 /***********************************************************************************************************************************
618 There are two separate implementations because string objects can get the size very efficiently whereas the zero-terminated strings
619 would need a call to strlen().
620 ***********************************************************************************************************************************/
621 bool
strEq(const String * this,const String * compare)622 strEq(const String *this, const String *compare)
623 {
624     FUNCTION_TEST_BEGIN();
625         FUNCTION_TEST_PARAM(STRING, this);
626         FUNCTION_TEST_PARAM(STRING, compare);
627     FUNCTION_TEST_END();
628 
629     bool result = false;
630 
631     if (this != NULL && compare != NULL)
632     {
633         if (strSize(this) == strSize(compare))
634             result = strcmp(strZ(this), strZ(compare)) == 0;
635     }
636     else
637         result = this == NULL && compare == NULL;
638 
639     FUNCTION_TEST_RETURN(result);
640 }
641 
642 bool
strEqZ(const String * this,const char * compare)643 strEqZ(const String *this, const char *compare)
644 {
645     FUNCTION_TEST_BEGIN();
646         FUNCTION_TEST_PARAM(STRING, this);
647         FUNCTION_TEST_PARAM(STRINGZ, compare);
648     FUNCTION_TEST_END();
649 
650     ASSERT(this != NULL);
651     ASSERT(compare != NULL);
652 
653     FUNCTION_TEST_RETURN(strcmp(strZ(this), compare) == 0);
654 }
655 
656 /**********************************************************************************************************************************/
657 String *
strFirstUpper(String * this)658 strFirstUpper(String *this)
659 {
660     FUNCTION_TEST_BEGIN();
661         FUNCTION_TEST_PARAM(STRING, this);
662     FUNCTION_TEST_END();
663 
664     ASSERT(this != NULL);
665 
666     if (strSize(this) > 0)
667         this->pub.buffer[0] = (char)toupper(this->pub.buffer[0]);
668 
669     FUNCTION_TEST_RETURN(this);
670 }
671 
672 /**********************************************************************************************************************************/
673 String *
strFirstLower(String * this)674 strFirstLower(String *this)
675 {
676     FUNCTION_TEST_BEGIN();
677         FUNCTION_TEST_PARAM(STRING, this);
678     FUNCTION_TEST_END();
679 
680     ASSERT(this != NULL);
681 
682     if (strSize(this) > 0)
683         this->pub.buffer[0] = (char)tolower(this->pub.buffer[0]);
684 
685     FUNCTION_TEST_RETURN(this);
686 }
687 
688 /**********************************************************************************************************************************/
689 String *
strUpper(String * this)690 strUpper(String *this)
691 {
692     FUNCTION_TEST_BEGIN();
693         FUNCTION_TEST_PARAM(STRING, this);
694     FUNCTION_TEST_END();
695 
696     ASSERT(this != NULL);
697 
698     if (strSize(this) > 0)
699         for (size_t idx = 0; idx <= strSize(this); idx++)
700             this->pub.buffer[idx] = (char)toupper(this->pub.buffer[idx]);
701 
702     FUNCTION_TEST_RETURN(this);
703 }
704 
705 /**********************************************************************************************************************************/
706 String *
strLower(String * this)707 strLower(String *this)
708 {
709     FUNCTION_TEST_BEGIN();
710         FUNCTION_TEST_PARAM(STRING, this);
711     FUNCTION_TEST_END();
712 
713     ASSERT(this != NULL);
714 
715     if (strSize(this) > 0)
716         for (size_t idx = 0; idx <= strSize(this); idx++)
717             this->pub.buffer[idx] = (char)tolower(this->pub.buffer[idx]);
718 
719     FUNCTION_TEST_RETURN(this);
720 }
721 
722 /**********************************************************************************************************************************/
723 String *
strPath(const String * this)724 strPath(const String *this)
725 {
726     FUNCTION_TEST_BEGIN();
727         FUNCTION_TEST_PARAM(STRING, this);
728     FUNCTION_TEST_END();
729 
730     ASSERT(this != NULL);
731 
732     const char *end = this->pub.buffer + strSize(this);
733 
734     while (end > this->pub.buffer && *(end - 1) != '/')
735         end--;
736 
737     FUNCTION_TEST_RETURN(
738         strNewN(
739             this->pub.buffer,
740             end - this->pub.buffer <= 1 ? (size_t)(end - this->pub.buffer) : (size_t)(end - this->pub.buffer - 1)));
741 }
742 
743 /**********************************************************************************************************************************/
744 String *
strPathAbsolute(const String * this,const String * base)745 strPathAbsolute(const String *this, const String *base)
746 {
747     FUNCTION_TEST_BEGIN();
748         FUNCTION_TEST_PARAM(STRING, this);
749         FUNCTION_TEST_PARAM(STRING, base);
750     FUNCTION_TEST_END();
751 
752     ASSERT(this != NULL);
753 
754     String *result = NULL;
755 
756     // Path is already absolute so just return it
757     if (strBeginsWith(this, FSLASH_STR))
758     {
759         result = strDup(this);
760     }
761     // Else we'll need to construct the absolute path.  You would hope we could use realpath() here but it is so broken in the
762     // Posix spec that is seems best avoided.
763     else
764     {
765         ASSERT(base != NULL);
766 
767         // Base must be absolute to start
768         if (!strBeginsWith(base, FSLASH_STR))
769             THROW_FMT(AssertError, "base path '%s' is not absolute", strZ(base));
770 
771         MEM_CONTEXT_TEMP_BEGIN()
772         {
773             StringList *baseList = strLstNewSplit(base, FSLASH_STR);
774             StringList *pathList = strLstNewSplit(this, FSLASH_STR);
775 
776             while (!strLstEmpty(pathList))
777             {
778                 const String *pathPart = strLstGet(pathList, 0);
779 
780                 // If the last part is empty
781                 if (strSize(pathPart) == 0)
782                 {
783                     // Allow when this is the last part since it just means there was a trailing /
784                     if (strLstSize(pathList) == 1)
785                     {
786                         strLstRemoveIdx(pathList, 0);
787                         break;
788                     }
789 
790                     THROW_FMT(AssertError, "'%s' is not a valid relative path", strZ(this));
791                 }
792 
793                 if (strEq(pathPart, DOTDOT_STR))
794                 {
795                     const String *basePart = strLstGet(baseList, strLstSize(baseList) - 1);
796 
797                     if (strSize(basePart) == 0)
798                         THROW_FMT(AssertError, "relative path '%s' goes back too far in base path '%s'", strZ(this), strZ(base));
799 
800                     strLstRemoveIdx(baseList, strLstSize(baseList) - 1);
801                 }
802                 else if (!strEq(pathPart, DOT_STR))
803                     strLstAdd(baseList, pathPart);
804 
805                 strLstRemoveIdx(pathList, 0);
806             }
807 
808             MEM_CONTEXT_PRIOR_BEGIN()
809             {
810                 if (strLstSize(baseList) == 1)
811                     result = strDup(FSLASH_STR);
812                 else
813                     result = strLstJoin(baseList, "/");
814             }
815             MEM_CONTEXT_PRIOR_END();
816         }
817         MEM_CONTEXT_TEMP_END();
818     }
819 
820     // There should not be any stray .. or // in the final result
821     if (strstr(strZ(result), "/..") != NULL || strstr(strZ(result), "//") != NULL)
822         THROW_FMT(AssertError, "result path '%s' is not absolute", strZ(result));
823 
824     FUNCTION_TEST_RETURN(result);
825 }
826 
827 /**********************************************************************************************************************************/
828 const char *
strZNull(const String * this)829 strZNull(const String *this)
830 {
831     FUNCTION_TEST_BEGIN();
832         FUNCTION_TEST_PARAM(STRING, this);
833     FUNCTION_TEST_END();
834 
835     FUNCTION_TEST_RETURN(this == NULL ? NULL : strZ(this));
836 }
837 
838 /**********************************************************************************************************************************/
839 String *
strQuote(const String * this,const String * quote)840 strQuote(const String *this, const String *quote)
841 {
842     FUNCTION_TEST_BEGIN();
843         FUNCTION_TEST_PARAM(STRING, this);
844         FUNCTION_TEST_PARAM(STRING, quote);
845     FUNCTION_TEST_END();
846 
847     ASSERT(this != NULL);
848     ASSERT(quote != NULL);
849 
850     FUNCTION_TEST_RETURN(strQuoteZ(this, strZ(quote)));
851 }
852 
853 String *
strQuoteZ(const String * this,const char * quote)854 strQuoteZ(const String *this, const char *quote)
855 {
856     FUNCTION_TEST_BEGIN();
857         FUNCTION_TEST_PARAM(STRING, this);
858         FUNCTION_TEST_PARAM(STRINGZ, quote);
859     FUNCTION_TEST_END();
860 
861     ASSERT(this != NULL);
862     ASSERT(quote != NULL);
863 
864     FUNCTION_TEST_RETURN(strNewFmt("%s%s%s", quote, strZ(this), quote));
865 }
866 
867 /**********************************************************************************************************************************/
868 String *
strReplaceChr(String * this,char find,char replace)869 strReplaceChr(String *this, char find, char replace)
870 {
871     FUNCTION_TEST_BEGIN();
872         FUNCTION_TEST_PARAM(STRING, this);
873         FUNCTION_TEST_PARAM(CHAR, find);
874         FUNCTION_TEST_PARAM(CHAR, replace);
875     FUNCTION_TEST_END();
876 
877     ASSERT(this != NULL);
878 
879     for (size_t stringIdx = 0; stringIdx < strSize(this); stringIdx++)
880     {
881         if (this->pub.buffer[stringIdx] == find)
882             this->pub.buffer[stringIdx] = replace;
883     }
884 
885     FUNCTION_TEST_RETURN(this);
886 }
887 
888 /**********************************************************************************************************************************/
889 String *
strSub(const String * this,size_t start)890 strSub(const String *this, size_t start)
891 {
892     FUNCTION_TEST_BEGIN();
893         FUNCTION_TEST_PARAM(STRING, this);
894         FUNCTION_TEST_PARAM(SIZE, start);
895     FUNCTION_TEST_END();
896 
897     ASSERT(this != NULL);
898     ASSERT(start <= this->pub.size);
899 
900     FUNCTION_TEST_RETURN(strSubN(this, start, strSize(this) - start));
901 }
902 
903 /**********************************************************************************************************************************/
904 String *
strSubN(const String * this,size_t start,size_t size)905 strSubN(const String *this, size_t start, size_t size)
906 {
907     FUNCTION_TEST_BEGIN();
908         FUNCTION_TEST_PARAM(STRING, this);
909         FUNCTION_TEST_PARAM(SIZE, start);
910         FUNCTION_TEST_PARAM(SIZE, size);
911     FUNCTION_TEST_END();
912 
913     ASSERT(this != NULL);
914     ASSERT(start <= strSize(this));
915     ASSERT(start + size <= strSize(this));
916 
917     FUNCTION_TEST_RETURN(strNewN(strZ(this) + start, size));
918 }
919 
920 /**********************************************************************************************************************************/
921 String *
strTrim(String * this)922 strTrim(String *this)
923 {
924     FUNCTION_TEST_BEGIN();
925         FUNCTION_TEST_PARAM(STRING, this);
926     FUNCTION_TEST_END();
927 
928     ASSERT(this != NULL);
929 
930     // Nothing to trim if size is zero
931     if (strSize(this) > 0)
932     {
933         // Find the beginning of the string skipping all whitespace
934         char *begin = this->pub.buffer;
935 
936         while (*begin != 0 && (*begin == ' ' || *begin == '\t' || *begin == '\r' || *begin == '\n'))
937             begin++;
938 
939         // Find the end of the string skipping all whitespace
940         char *end = this->pub.buffer + (strSize(this) - 1);
941 
942         while (end > begin && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n'))
943             end--;
944 
945         // Is there anything to trim?
946         size_t newSize = (size_t)(end - begin + 1);
947 
948         if (begin != this->pub.buffer || newSize < strSize(this))
949         {
950             // Calculate new size
951             this->pub.size = (unsigned int)newSize;
952 
953             // Move the substr to the beginning of the buffer
954             memmove(this->pub.buffer, begin, strSize(this));
955             this->pub.buffer[strSize(this)] = 0;
956             this->pub.extra = 0;
957 
958             MEM_CONTEXT_BEGIN(this->memContext)
959             {
960                 // Resize the buffer
961                 this->pub.buffer = memResize(this->pub.buffer, strSize(this) + 1);
962             }
963             MEM_CONTEXT_END();
964         }
965     }
966 
967     FUNCTION_TEST_RETURN(this);
968 }
969 
970 /**********************************************************************************************************************************/
971 int
strChr(const String * this,char chr)972 strChr(const String *this, char chr)
973 {
974     FUNCTION_TEST_BEGIN();
975         FUNCTION_TEST_PARAM(STRING, this);
976         FUNCTION_TEST_PARAM(CHAR, chr);
977     FUNCTION_TEST_END();
978 
979     ASSERT(this != NULL);
980 
981     int result = -1;
982 
983     if (strSize(this) > 0)
984     {
985         const char *ptr = strchr(this->pub.buffer, chr);
986 
987         if (ptr != NULL)
988             result = (int)(ptr - this->pub.buffer);
989     }
990 
991     FUNCTION_TEST_RETURN(result);
992 }
993 
994 /**********************************************************************************************************************************/
995 String *
strTrunc(String * this,int idx)996 strTrunc(String *this, int idx)
997 {
998     FUNCTION_TEST_BEGIN();
999         FUNCTION_TEST_PARAM(STRING, this);
1000     FUNCTION_TEST_END();
1001 
1002     ASSERT(this != NULL);
1003     ASSERT(idx >= 0 && (size_t)idx <= strSize(this));
1004 
1005     if (strSize(this) > 0)
1006     {
1007         // Reset the size to end at the index
1008         this->pub.size = (unsigned int)(idx);
1009         this->pub.buffer[strSize(this)] = 0;
1010         this->pub.extra = 0;
1011 
1012         MEM_CONTEXT_BEGIN(this->memContext)
1013         {
1014             // Resize the buffer
1015             this->pub.buffer = memResize(this->pub.buffer, strSize(this) + 1);
1016         }
1017         MEM_CONTEXT_END();
1018     }
1019 
1020     FUNCTION_TEST_RETURN(this);
1021 }
1022 
1023 /***********************************************************************************************************************************
1024 Convert an object to a zero-terminated string for logging
1025 ***********************************************************************************************************************************/
strObjToLog(const void * object,StrObjToLogFormat formatFunc,char * buffer,size_t bufferSize)1026 size_t strObjToLog(const void *object, StrObjToLogFormat formatFunc, char *buffer, size_t bufferSize)
1027 {
1028     size_t result = 0;
1029 
1030     MEM_CONTEXT_TEMP_BEGIN()
1031     {
1032         result = (size_t)snprintf(buffer, bufferSize, "%s", object == NULL ? NULL_Z : strZ(formatFunc(object)));
1033     }
1034     MEM_CONTEXT_TEMP_END();
1035 
1036     return result;
1037 }
1038 
1039 /**********************************************************************************************************************************/
1040 String *
strToLog(const String * this)1041 strToLog(const String *this)
1042 {
1043     return this == NULL ? strDup(NULL_STR) : strNewFmt("{\"%s\"}", strZ(this));
1044 }
1045 
1046 /**********************************************************************************************************************************/
1047 String *
strSizeFormat(const uint64_t size)1048 strSizeFormat(const uint64_t size)
1049 {
1050     FUNCTION_TEST_BEGIN();
1051         FUNCTION_TEST_PARAM(UINT64, size);
1052 
1053     FUNCTION_TEST_END();
1054 
1055     String *result = NULL;
1056 
1057     if (size < 1024)
1058         result = strNewFmt("%" PRIu64 "B", size);
1059     else if (size < (1024 * 1024))
1060     {
1061         if ((uint64_t)((double)size / 102.4) % 10 != 0)
1062             result = strNewFmt("%.1lfKB", (double)size / 1024);
1063         else
1064             result = strNewFmt("%" PRIu64 "KB", size / 1024);
1065     }
1066     else if (size < (1024 * 1024 * 1024))
1067     {
1068         if ((uint64_t)((double)size / (1024 * 102.4)) % 10 != 0)
1069             result = strNewFmt("%.1lfMB", (double)size / (1024 * 1024));
1070         else
1071             result = strNewFmt("%" PRIu64 "MB", size / (1024 * 1024));
1072     }
1073     else
1074     {
1075         if ((uint64_t)((double)size / (1024 * 1024 * 102.4)) % 10 != 0)
1076             result = strNewFmt("%.1lfGB", (double)size / (1024 * 1024 * 1024));
1077         else
1078             result = strNewFmt("%" PRIu64 "GB", size / (1024 * 1024 * 1024));
1079     }
1080 
1081     FUNCTION_TEST_RETURN(result);
1082 }
1083 
1084 /***********************************************************************************************************************************
1085 Free the string
1086 ***********************************************************************************************************************************/
1087 void
strFree(String * this)1088 strFree(String *this)
1089 {
1090     FUNCTION_TEST_BEGIN();
1091         FUNCTION_TEST_PARAM(STRING, this);
1092     FUNCTION_TEST_END();
1093 
1094     if (this != NULL)
1095     {
1096         MEM_CONTEXT_BEGIN(this->memContext)
1097         {
1098             memFree(this->pub.buffer);
1099             memFree(this);
1100         }
1101         MEM_CONTEXT_END();
1102     }
1103 
1104     FUNCTION_TEST_RETURN_VOID();
1105 }
1106