1 /*
2 * String functions for CUPS.
3 *
4 * Copyright © 2020-2024 by OpenPrinting.
5 * Copyright © 2007-2019 by Apple Inc.
6 * Copyright © 1997-2007 by Easy Software Products.
7 *
8 * Licensed under Apache License v2.0. See the file "LICENSE" for more
9 * information.
10 */
11
12 /*
13 * Include necessary headers...
14 */
15
16 #define _CUPS_STRING_C_
17 #include "cups-private.h"
18 #include "debug-internal.h"
19 #include <stddef.h>
20 #include <limits.h>
21
22
23 /*
24 * Local globals...
25 */
26
27 static _cups_mutex_t sp_mutex = _CUPS_MUTEX_INITIALIZER;
28 /* Mutex to control access to pool */
29 static cups_array_t *stringpool = NULL;
30 /* Global string pool */
31
32
33 /*
34 * Local functions...
35 */
36
37 static int compare_sp_items(_cups_sp_item_t *a, _cups_sp_item_t *b);
38
39
40 /*
41 * '_cupsStrAlloc()' - Allocate/reference a string.
42 */
43
44 char * /* O - String pointer */
_cupsStrAlloc(const char * s)45 _cupsStrAlloc(const char *s) /* I - String */
46 {
47 size_t slen; /* Length of string */
48 _cups_sp_item_t *item, /* String pool item */
49 *key; /* Search key */
50
51
52 /*
53 * Range check input...
54 */
55
56 if (!s)
57 return (NULL);
58
59 /*
60 * Get the string pool...
61 */
62
63 _cupsMutexLock(&sp_mutex);
64
65 if (!stringpool)
66 stringpool = cupsArrayNew((cups_array_func_t)compare_sp_items, NULL);
67
68 if (!stringpool)
69 {
70 _cupsMutexUnlock(&sp_mutex);
71
72 return (NULL);
73 }
74
75 /*
76 * See if the string is already in the pool...
77 */
78
79 key = (_cups_sp_item_t *)(s - offsetof(_cups_sp_item_t, str));
80
81 if ((item = (_cups_sp_item_t *)cupsArrayFind(stringpool, key)) != NULL)
82 {
83 /*
84 * Found it, return the cached string...
85 */
86
87 item->ref_count ++;
88
89 #ifdef DEBUG_GUARDS
90 DEBUG_printf(("5_cupsStrAlloc: Using string %p(%s) for \"%s\", guard=%08x, "
91 "ref_count=%d", item, item->str, s, item->guard,
92 item->ref_count));
93
94 if (item->guard != _CUPS_STR_GUARD)
95 abort();
96 #endif /* DEBUG_GUARDS */
97
98 _cupsMutexUnlock(&sp_mutex);
99
100 return (item->str);
101 }
102
103 /*
104 * Not found, so allocate a new one...
105 */
106
107 slen = strlen(s);
108 item = (_cups_sp_item_t *)calloc(1, sizeof(_cups_sp_item_t) + slen);
109 if (!item)
110 {
111 _cupsMutexUnlock(&sp_mutex);
112
113 return (NULL);
114 }
115
116 item->ref_count = 1;
117 memcpy(item->str, s, slen + 1);
118
119 #ifdef DEBUG_GUARDS
120 item->guard = _CUPS_STR_GUARD;
121
122 DEBUG_printf(("5_cupsStrAlloc: Created string %p(%s) for \"%s\", guard=%08x, "
123 "ref_count=%d", item, item->str, s, item->guard,
124 item->ref_count));
125 #endif /* DEBUG_GUARDS */
126
127 /*
128 * Add the string to the pool and return it...
129 */
130
131 cupsArrayAdd(stringpool, item);
132
133 _cupsMutexUnlock(&sp_mutex);
134
135 return (item->str);
136 }
137
138
139 /*
140 * '_cupsStrDate()' - Return a localized date for a given time value.
141 *
142 * This function works around the locale encoding issues of strftime...
143 */
144
145 char * /* O - Buffer */
_cupsStrDate(char * buf,size_t bufsize,time_t timeval)146 _cupsStrDate(char *buf, /* I - Buffer */
147 size_t bufsize, /* I - Size of buffer */
148 time_t timeval) /* I - Time value */
149 {
150 struct tm date; /* Local date/time */
151 char temp[1024]; /* Temporary buffer */
152 _cups_globals_t *cg = _cupsGlobals(); /* Per-thread globals */
153
154
155 if (!cg->lang_default)
156 cg->lang_default = cupsLangDefault();
157
158 localtime_r(&timeval, &date);
159
160 if (cg->lang_default->encoding != CUPS_UTF8)
161 {
162 strftime(temp, sizeof(temp), "%c", &date);
163 cupsCharsetToUTF8((cups_utf8_t *)buf, temp, (int)bufsize, cg->lang_default->encoding);
164 }
165 else
166 strftime(buf, bufsize, "%c", &date);
167
168 return (buf);
169 }
170
171
172 /*
173 * '_cupsStrFlush()' - Flush the string pool.
174 */
175
176 void
_cupsStrFlush(void)177 _cupsStrFlush(void)
178 {
179 _cups_sp_item_t *item; /* Current item */
180
181
182 DEBUG_printf(("4_cupsStrFlush: %d strings in array",
183 cupsArrayCount(stringpool)));
184
185 _cupsMutexLock(&sp_mutex);
186
187 for (item = (_cups_sp_item_t *)cupsArrayFirst(stringpool);
188 item;
189 item = (_cups_sp_item_t *)cupsArrayNext(stringpool))
190 free(item);
191
192 cupsArrayDelete(stringpool);
193 stringpool = NULL;
194
195 _cupsMutexUnlock(&sp_mutex);
196 }
197
198
199 /*
200 * '_cupsStrFormatd()' - Format a floating-point number.
201 */
202
203 char * /* O - Pointer to end of string */
_cupsStrFormatd(char * buf,char * bufend,double number,struct lconv * loc)204 _cupsStrFormatd(char *buf, /* I - String */
205 char *bufend, /* I - End of string buffer */
206 double number, /* I - Number to format */
207 struct lconv *loc) /* I - Locale data */
208 {
209 char *bufptr, /* Pointer into buffer */
210 temp[1024], /* Temporary string */
211 *tempdec, /* Pointer to decimal point */
212 *tempptr; /* Pointer into temporary string */
213 const char *dec; /* Decimal point */
214 int declen; /* Length of decimal point */
215
216
217 /*
218 * Format the number using the "%.12f" format and then eliminate
219 * unnecessary trailing 0's.
220 */
221
222 snprintf(temp, sizeof(temp), "%.12f", number);
223 for (tempptr = temp + strlen(temp) - 1;
224 tempptr > temp && *tempptr == '0';
225 *tempptr-- = '\0');
226
227 /*
228 * Next, find the decimal point...
229 */
230
231 if (loc && loc->decimal_point)
232 {
233 dec = loc->decimal_point;
234 declen = (int)strlen(dec);
235 }
236 else
237 {
238 dec = ".";
239 declen = 1;
240 }
241
242 if (declen == 1)
243 tempdec = strchr(temp, *dec);
244 else
245 tempdec = strstr(temp, dec);
246
247 /*
248 * Copy everything up to the decimal point...
249 */
250
251 if (tempdec)
252 {
253 for (tempptr = temp, bufptr = buf;
254 tempptr < tempdec && bufptr < bufend;
255 *bufptr++ = *tempptr++);
256
257 tempptr += declen;
258
259 if (*tempptr && bufptr < bufend)
260 {
261 *bufptr++ = '.';
262
263 while (*tempptr && bufptr < bufend)
264 *bufptr++ = *tempptr++;
265 }
266
267 *bufptr = '\0';
268 }
269 else
270 {
271 strlcpy(buf, temp, (size_t)(bufend - buf + 1));
272 bufptr = buf + strlen(buf);
273 }
274
275 return (bufptr);
276 }
277
278
279 /*
280 * '_cupsStrFree()' - Free/dereference a string.
281 */
282
283 void
_cupsStrFree(const char * s)284 _cupsStrFree(const char *s) /* I - String to free */
285 {
286 _cups_sp_item_t *item, /* String pool item */
287 *key; /* Search key */
288
289
290 /*
291 * Range check input...
292 */
293
294 if (!s)
295 return;
296
297 /*
298 * Check the string pool...
299 *
300 * We don't need to lock the mutex yet, as we only want to know if
301 * the stringpool is initialized. The rest of the code will still
302 * work if it is initialized before we lock...
303 */
304
305 if (!stringpool)
306 return;
307
308 /*
309 * See if the string is already in the pool...
310 */
311
312 _cupsMutexLock(&sp_mutex);
313
314 key = (_cups_sp_item_t *)(s - offsetof(_cups_sp_item_t, str));
315
316 if ((item = (_cups_sp_item_t *)cupsArrayFind(stringpool, key)) != NULL &&
317 item == key)
318 {
319 /*
320 * Found it, dereference...
321 */
322
323 #ifdef DEBUG_GUARDS
324 if (key->guard != _CUPS_STR_GUARD)
325 {
326 DEBUG_printf(("5_cupsStrFree: Freeing string %p(%s), guard=%08x, ref_count=%d", key, key->str, key->guard, key->ref_count));
327 abort();
328 }
329 #endif /* DEBUG_GUARDS */
330
331 item->ref_count --;
332
333 if (!item->ref_count)
334 {
335 /*
336 * Remove and free...
337 */
338
339 cupsArrayRemove(stringpool, item);
340
341 free(item);
342 }
343 }
344
345 _cupsMutexUnlock(&sp_mutex);
346 }
347
348
349 /*
350 * '_cupsStrRetain()' - Increment the reference count of a string.
351 *
352 * Note: This function does not verify that the passed pointer is in the
353 * string pool, so any calls to it MUST know they are passing in a
354 * good pointer.
355 */
356
357 char * /* O - Pointer to string */
_cupsStrRetain(const char * s)358 _cupsStrRetain(const char *s) /* I - String to retain */
359 {
360 _cups_sp_item_t *item; /* Pointer to string pool item */
361
362
363 if (s)
364 {
365 item = (_cups_sp_item_t *)(s - offsetof(_cups_sp_item_t, str));
366
367 #ifdef DEBUG_GUARDS
368 if (item->guard != _CUPS_STR_GUARD)
369 {
370 DEBUG_printf(("5_cupsStrRetain: Retaining string %p(%s), guard=%08x, "
371 "ref_count=%d", item, s, item->guard, item->ref_count));
372 abort();
373 }
374 #endif /* DEBUG_GUARDS */
375
376 _cupsMutexLock(&sp_mutex);
377
378 item->ref_count ++;
379
380 _cupsMutexUnlock(&sp_mutex);
381 }
382
383 return ((char *)s);
384 }
385
386
387 /*
388 * '_cupsStrScand()' - Scan a string for a floating-point number.
389 *
390 * This function handles the locale-specific BS so that a decimal
391 * point is always the period (".")...
392 */
393
394 double /* O - Number */
_cupsStrScand(const char * buf,char ** bufptr,struct lconv * loc)395 _cupsStrScand(const char *buf, /* I - Pointer to number */
396 char **bufptr, /* O - New pointer or NULL on error */
397 struct lconv *loc) /* I - Locale data */
398 {
399 char temp[1024], /* Temporary buffer */
400 *tempptr; /* Pointer into temporary buffer */
401
402
403 /*
404 * Range check input...
405 */
406
407 if (!buf)
408 return (0.0);
409
410 /*
411 * Skip leading whitespace...
412 */
413
414 while (_cups_isspace(*buf))
415 buf ++;
416
417 /*
418 * Copy leading sign, numbers, period, and then numbers...
419 */
420
421 tempptr = temp;
422 if (*buf == '-' || *buf == '+')
423 *tempptr++ = *buf++;
424
425 while (isdigit(*buf & 255))
426 if (tempptr < (temp + sizeof(temp) - 1))
427 *tempptr++ = *buf++;
428 else
429 {
430 if (bufptr)
431 *bufptr = NULL;
432
433 return (0.0);
434 }
435
436 if (*buf == '.')
437 {
438 /*
439 * Read fractional portion of number...
440 */
441
442 buf ++;
443
444 if (loc && loc->decimal_point)
445 {
446 strlcpy(tempptr, loc->decimal_point, sizeof(temp) - (size_t)(tempptr - temp));
447 tempptr += strlen(tempptr);
448 }
449 else if (tempptr < (temp + sizeof(temp) - 1))
450 *tempptr++ = '.';
451 else
452 {
453 if (bufptr)
454 *bufptr = NULL;
455
456 return (0.0);
457 }
458
459 while (isdigit(*buf & 255))
460 if (tempptr < (temp + sizeof(temp) - 1))
461 *tempptr++ = *buf++;
462 else
463 {
464 if (bufptr)
465 *bufptr = NULL;
466
467 return (0.0);
468 }
469 }
470
471 if (*buf == 'e' || *buf == 'E')
472 {
473 /*
474 * Read exponent...
475 */
476
477 if (tempptr < (temp + sizeof(temp) - 1))
478 *tempptr++ = *buf++;
479 else
480 {
481 if (bufptr)
482 *bufptr = NULL;
483
484 return (0.0);
485 }
486
487 if (*buf == '+' || *buf == '-')
488 {
489 if (tempptr < (temp + sizeof(temp) - 1))
490 *tempptr++ = *buf++;
491 else
492 {
493 if (bufptr)
494 *bufptr = NULL;
495
496 return (0.0);
497 }
498 }
499
500 while (isdigit(*buf & 255))
501 if (tempptr < (temp + sizeof(temp) - 1))
502 *tempptr++ = *buf++;
503 else
504 {
505 if (bufptr)
506 *bufptr = NULL;
507
508 return (0.0);
509 }
510 }
511
512 /*
513 * Nul-terminate the temporary string and return the value...
514 */
515
516 if (bufptr)
517 *bufptr = (char *)buf;
518
519 *tempptr = '\0';
520
521 return (atof(temp));
522 }
523
524
525 /*
526 * '_cupsStrStatistics()' - Return allocation statistics for string pool.
527 */
528
529 size_t /* O - Number of strings */
_cupsStrStatistics(size_t * alloc_bytes,size_t * total_bytes)530 _cupsStrStatistics(size_t *alloc_bytes, /* O - Allocated bytes */
531 size_t *total_bytes) /* O - Total string bytes */
532 {
533 size_t count, /* Number of strings */
534 abytes, /* Allocated string bytes */
535 tbytes, /* Total string bytes */
536 len; /* Length of string */
537 _cups_sp_item_t *item; /* Current item */
538
539
540 /*
541 * Loop through strings in pool, counting everything up...
542 */
543
544 _cupsMutexLock(&sp_mutex);
545
546 for (count = 0, abytes = 0, tbytes = 0,
547 item = (_cups_sp_item_t *)cupsArrayFirst(stringpool);
548 item;
549 item = (_cups_sp_item_t *)cupsArrayNext(stringpool))
550 {
551 /*
552 * Count allocated memory, using a 64-bit aligned buffer as a basis.
553 */
554
555 count += item->ref_count;
556 len = (strlen(item->str) + 8) & (size_t)~7;
557 abytes += sizeof(_cups_sp_item_t) + len;
558 tbytes += item->ref_count * len;
559 }
560
561 _cupsMutexUnlock(&sp_mutex);
562
563 /*
564 * Return values...
565 */
566
567 if (alloc_bytes)
568 *alloc_bytes = abytes;
569
570 if (total_bytes)
571 *total_bytes = tbytes;
572
573 return (count);
574 }
575
576
577 /*
578 * '_cups_strcpy()' - Copy a string allowing for overlapping strings.
579 */
580
581 void
_cups_strcpy(char * dst,const char * src)582 _cups_strcpy(char *dst, /* I - Destination string */
583 const char *src) /* I - Source string */
584 {
585 while (*src)
586 *dst++ = *src++;
587
588 *dst = '\0';
589 }
590
591
592 /*
593 * '_cups_strdup()' - Duplicate a string.
594 */
595
596 #ifndef HAVE_STRDUP
597 char * /* O - New string pointer */
_cups_strdup(const char * s)598 _cups_strdup(const char *s) /* I - String to duplicate */
599 {
600 char *t; /* New string pointer */
601 size_t slen; /* Length of string */
602
603
604 if (!s)
605 return (NULL);
606
607 slen = strlen(s);
608 if ((t = malloc(slen + 1)) == NULL)
609 return (NULL);
610
611 return (memcpy(t, s, slen + 1));
612 }
613 #endif /* !HAVE_STRDUP */
614
615
616 /*
617 * '_cups_strcasecmp()' - Do a case-insensitive comparison.
618 */
619
620 int /* O - Result of comparison (-1, 0, or 1) */
_cups_strcasecmp(const char * s,const char * t)621 _cups_strcasecmp(const char *s, /* I - First string */
622 const char *t) /* I - Second string */
623 {
624 while (*s != '\0' && *t != '\0')
625 {
626 if (_cups_tolower(*s) < _cups_tolower(*t))
627 return (-1);
628 else if (_cups_tolower(*s) > _cups_tolower(*t))
629 return (1);
630
631 s ++;
632 t ++;
633 }
634
635 if (*s == '\0' && *t == '\0')
636 return (0);
637 else if (*s != '\0')
638 return (1);
639 else
640 return (-1);
641 }
642
643 /*
644 * '_cups_strncasecmp()' - Do a case-insensitive comparison on up to N chars.
645 */
646
647 int /* O - Result of comparison (-1, 0, or 1) */
_cups_strncasecmp(const char * s,const char * t,size_t n)648 _cups_strncasecmp(const char *s, /* I - First string */
649 const char *t, /* I - Second string */
650 size_t n) /* I - Maximum number of characters to compare */
651 {
652 while (*s != '\0' && *t != '\0' && n > 0)
653 {
654 if (_cups_tolower(*s) < _cups_tolower(*t))
655 return (-1);
656 else if (_cups_tolower(*s) > _cups_tolower(*t))
657 return (1);
658
659 s ++;
660 t ++;
661 n --;
662 }
663
664 if (n == 0)
665 return (0);
666 else if (*s == '\0' && *t == '\0')
667 return (0);
668 else if (*s != '\0')
669 return (1);
670 else
671 return (-1);
672 }
673
674
675 #ifndef HAVE_STRLCAT
676 /*
677 * '_cups_strlcat()' - Safely concatenate two strings.
678 */
679
680 size_t /* O - Length of string */
_cups_strlcat(char * dst,const char * src,size_t size)681 _cups_strlcat(char *dst, /* O - Destination string */
682 const char *src, /* I - Source string */
683 size_t size) /* I - Size of destination string buffer */
684 {
685 size_t srclen; /* Length of source string */
686 size_t dstlen; /* Length of destination string */
687
688
689 /*
690 * Figure out how much room is left...
691 */
692
693 dstlen = strlen(dst);
694
695 if (size < (dstlen + 1))
696 return (dstlen); /* No room, return immediately... */
697
698 size -= dstlen + 1;
699
700 /*
701 * Figure out how much room is needed...
702 */
703
704 srclen = strlen(src);
705
706 /*
707 * Copy the appropriate amount...
708 */
709
710 if (srclen > size)
711 srclen = size;
712
713 memmove(dst + dstlen, src, srclen);
714 dst[dstlen + srclen] = '\0';
715
716 return (dstlen + srclen);
717 }
718 #endif /* !HAVE_STRLCAT */
719
720
721 #ifndef HAVE_STRLCPY
722 /*
723 * '_cups_strlcpy()' - Safely copy two strings.
724 */
725
726 size_t /* O - Length of string */
_cups_strlcpy(char * dst,const char * src,size_t size)727 _cups_strlcpy(char *dst, /* O - Destination string */
728 const char *src, /* I - Source string */
729 size_t size) /* I - Size of destination string buffer */
730 {
731 size_t srclen; /* Length of source string */
732
733
734 if (size == 0)
735 return (0);
736
737 /*
738 * Figure out how much room is needed...
739 */
740
741 size --;
742
743 srclen = strlen(src);
744
745 /*
746 * Copy the appropriate amount...
747 */
748
749 if (srclen > size)
750 srclen = size;
751
752 memmove(dst, src, srclen);
753 dst[srclen] = '\0';
754
755 return (srclen);
756 }
757 #endif /* !HAVE_STRLCPY */
758
759
760 /*
761 * 'compare_sp_items()' - Compare two string pool items...
762 */
763
764 static int /* O - Result of comparison */
compare_sp_items(_cups_sp_item_t * a,_cups_sp_item_t * b)765 compare_sp_items(_cups_sp_item_t *a, /* I - First item */
766 _cups_sp_item_t *b) /* I - Second item */
767 {
768 return (strcmp(a->str, b->str));
769 }
770