1 /*
2  * String utilities.
3  *
4  * Copyright (c) 2009  Marko Kreen
5  *
6  * Permission to use, copy, modify, and/or distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <usual/string.h>
20 
21 #include <locale.h>
22 #ifdef HAVE_XLOCALE_H
23 #include <xlocale.h>
24 #endif
25 #ifdef HAVE_LANGINFO_H
26 #include <langinfo.h>
27 #endif
28 
29 #include <usual/mbuf.h>
30 #include <usual/statlist.h>
31 #include <usual/ctype.h>
32 #include <usual/bytemap.h>
33 
34 /*
35  * Dynamic list of strings.
36  */
37 
38 struct StrList {
39 	struct StatList list;
40 	CxMem *ca;
41 };
42 
43 struct StrItem {
44 	struct List node;
45 	char *str;
46 };
47 
strlist_empty(struct StrList * slist)48 bool strlist_empty(struct StrList *slist)
49 {
50 	return statlist_empty(&slist->list);
51 }
52 
strlist_append(struct StrList * slist,const char * str)53 bool strlist_append(struct StrList *slist, const char *str)
54 {
55 	char *nstr = NULL;
56 	bool ok;
57 	if (str) {
58 		nstr = cx_strdup(slist->ca, str);
59 		if (!nstr)
60 			return false;
61 	}
62 	ok = strlist_append_ref(slist, nstr);
63 	if (!ok)
64 		cx_free(slist->ca, nstr);
65 	return ok;
66 }
67 
strlist_append_ref(struct StrList * slist,char * str)68 bool strlist_append_ref(struct StrList *slist, char *str)
69 {
70 	struct StrItem *item = cx_alloc(slist->ca, sizeof(*item));
71 	if (!item)
72 		return false;
73 	list_init(&item->node);
74 	item->str = str;
75 	statlist_append(&slist->list, &item->node);
76 	return true;
77 }
78 
strlist_pop(struct StrList * slist)79 char *strlist_pop(struct StrList *slist)
80 {
81 	struct StrItem *item;
82 	struct List *el;
83 	char *str;
84 
85 	el = statlist_pop(&slist->list);
86 	if (!el)
87 		return NULL;
88 
89 	item = container_of(el, struct StrItem, node);
90 	str = item->str;
91 	cx_free(slist->ca, item);
92 	return str;
93 }
94 
strlist_new(CxMem * ca)95 struct StrList *strlist_new(CxMem *ca)
96 {
97 	struct StrList *slist = cx_alloc0(ca, sizeof(*slist));
98 	if (!slist)
99 		return NULL;
100 	statlist_init(&slist->list, "strlist");
101 	slist->ca = ca;
102 	return slist;
103 }
104 
strlist_free(struct StrList * slist)105 void strlist_free(struct StrList *slist)
106 {
107 	char *s;
108 	if (!slist)
109 		return;
110 	while (!strlist_empty(slist)) {
111 		s = strlist_pop(slist);
112 		if (s)
113 			cx_free(slist->ca, s);
114 	}
115 	cx_free(slist->ca, slist);
116 }
117 
strlist_foreach(const struct StrList * slist,str_cb func,void * arg)118 bool strlist_foreach(const struct StrList *slist, str_cb func, void *arg)
119 {
120 	struct List *el;
121 	struct StrItem *item;
122 	statlist_for_each(el, &slist->list) {
123 		item = container_of(el, struct StrItem, node);
124 		if (!func(arg, item->str))
125 			return false;
126 	}
127 	return true;
128 }
129 
130 /*
131  * Parse comma separated words.
132  */
133 
skip_ws(const char * p)134 static inline const char *skip_ws(const char *p)
135 {
136 	while (*p && isspace(*p))
137 		p++;
138 	return p;
139 }
140 
141 
parse_word_list(const char * s,str_cb cb_func,void * cb_arg)142 bool parse_word_list(const char *s, str_cb cb_func, void *cb_arg)
143 {
144 	struct MBuf buf;
145 	const char *p = s;
146 	const char *start, *end;
147 
148 	mbuf_init_dynamic(&buf);
149 	while (*p) {
150 		/* parse word */
151 		p = skip_ws(p);
152 		start = p;
153 		while (*p && *p != ',')
154 			p++;
155 		end = p;
156 		while (end > start && isspace(*(end - 1)))
157 			end--;
158 
159 		/* parse comma */
160 		if (*p) {
161 			if (*p == ',') {
162 				p++;
163 			} else {
164 				goto failed_syntax;
165 			}
166 		}
167 
168 		/* extract string */
169 		if (!mbuf_write(&buf, start, end - start))
170 			goto failed;
171 		if (!mbuf_write_byte(&buf, 0))
172 			goto failed;
173 
174 		/* launch callback */
175 		if (!cb_func(cb_arg, (const char *)buf.data))
176 			goto failed;
177 
178 		/* reset */
179 		mbuf_rewind_writer(&buf);
180 	}
181 	mbuf_free(&buf);
182 	return true;
183 
184 failed_syntax:
185 	errno = EINVAL;
186 failed:
187 	mbuf_free(&buf);
188 	return false;
189 }
190 
191 /*
192  * Minimal spec-conforming implementations of strlcpy(), strlcat().
193  */
194 
195 #ifndef HAVE_STRLCPY
strlcpy(char * dst,const char * src,size_t n)196 size_t strlcpy(char *dst, const char *src, size_t n)
197 {
198 	size_t len = strlen(src);
199 	if (len < n) {
200 		memcpy(dst, src, len + 1);
201 	} else if (n > 0) {
202 		memcpy(dst, src, n - 1);
203 		dst[n - 1] = 0;
204 	}
205 	return len;
206 }
207 #endif
208 
209 #ifndef HAVE_STRLCAT
strlcat(char * dst,const char * src,size_t n)210 size_t strlcat(char *dst, const char *src, size_t n)
211 {
212 	size_t pos = 0;
213 	while (pos < n && dst[pos])
214 		pos++;
215 	return pos + strlcpy(dst + pos, src, n - pos);
216 }
217 #endif
218 
strpcpy(char * dst,const char * src,size_t n)219 char *strpcpy(char *dst, const char *src, size_t n)
220 {
221 	if (n == 0)
222 		return NULL;
223 	for (; n > 0; n--, dst++, src++) {
224 		if ((*dst = *src) == '\0')
225 			return dst;
226 	}
227 	dst[-1] = '\0';
228 	return NULL;
229 }
230 
strpcat(char * dst,const char * src,size_t n)231 char *strpcat(char *dst, const char *src, size_t n)
232 {
233 	size_t dstlen = strnlen(dst, n);
234 	if (dstlen < n)
235 		return strpcpy(dst + dstlen, src, n - dstlen);
236 	return NULL;
237 }
238 
239 #ifndef HAVE_MEMPCPY
mempcpy(void * dst,const void * src,size_t n)240 void *mempcpy(void *dst, const void *src, size_t n)
241 {
242 	memcpy(dst, src, n);
243 	return (char *)(dst) + n;
244 }
245 #endif
246 
247 #ifndef HAVE_MEMRCHR
memrchr(const void * s,int c,size_t n)248 void *memrchr(const void *s, int c, size_t n)
249 {
250 	const uint8_t *p = s;
251 	while (n--) {
252 		if (p[n] == c)
253 			return (void *)(p + n);
254 	}
255 	return NULL;
256 }
257 #endif
258 
259 #ifndef HAVE_MEMMEM
memmem(const void * haystack,size_t hlen,const void * needle,size_t nlen)260 void *memmem(const void *haystack, size_t hlen, const void *needle, size_t nlen)
261 {
262 	const uint8_t *s = haystack;
263 	const uint8_t *q = needle;
264 	const uint8_t *s2;
265 	size_t i;
266 
267 	if (nlen == 0)
268 		return (void *)haystack;
269 	if (nlen > hlen)
270 		return NULL;
271 	s2 = memchr(haystack, *q, hlen);
272 	if (!s2 || nlen == 1)
273 		return (void *)s2;
274 	for (i = s2 - s; i <= hlen - nlen; i++) {
275 		if (s[i] == q[0] && s[i+1] == q[1]) {
276 			if (memcmp(s + i + 2, q + 2, nlen - 2) == 0)
277 				return (void *)(s + i);
278 		}
279 	}
280 	return NULL;
281 }
282 #endif
283 
284 #ifndef HAVE_EXPLICIT_BZERO
285 
286 #if defined(_WIN32) && defined(SecureZeroMemory)
287 
explicit_bzero(void * buf,size_t len)288 void explicit_bzero(void *buf, size_t len)
289 {
290 	SecureZeroMemory(buf, len);
291 }
292 
293 #elif defined(HAVE_MEMSET_S)
294 
explicit_bzero(void * buf,size_t len)295 void explicit_bzero(void *buf, size_t len)
296 {
297 	memset_s(buf, len, 0, len);
298 }
299 
300 #else /* non-win32 */
301 
302 /* avoid link-time optimization */
303 #if defined(__GNUC__x) || __has_attribute(weak)
304 void __explicit_bzero_hack(void *, size_t);
__explicit_bzero_hack(void * buf,size_t len)305 __attribute__((weak)) void __explicit_bzero_hack(void *buf, size_t len) { }
306 #else
307 typedef void (*__explicit_bzero_cb_t)(void *, size_t);
__explicit_bzero_hack_cb(void * buf,size_t len)308 static void __explicit_bzero_hack_cb(void *buf, size_t len) { }
309 static volatile __explicit_bzero_cb_t __explicit_bzero_hack = __explicit_bzero_hack_cb;
310 #endif
311 
explicit_bzero(void * buf,size_t len)312 void explicit_bzero(void *buf, size_t len)
313 {
314 	memset(buf, 0, len);
315 	__explicit_bzero_hack(buf, len);
316 }
317 
318 #endif
319 #endif /* !_WIN32 */
320 
321 #ifndef HAVE_BASENAME
basename(const char * path)322 const char *basename(const char *path)
323 {
324 	const char *p, *p2;
325 	static char buf[256];
326 	unsigned len;
327 
328 	if (path == NULL || path[0] == 0)
329 		return memcpy(buf, ".", 2);
330 	if ((p = strrchr(path, '/')) == NULL)
331 		return path;
332 	if (p[1])
333 		return p + 1;
334 
335 	/* last char is '/' */
336 	for (p2 = p; p2 > path; p2--) {
337 		if (p2[-1] != '/') {
338 			len = p2 - path;
339 			if (len > sizeof(buf) - 1)
340 				len = sizeof(buf) - 1;
341 			memcpy(buf, p2 - len, len);
342 			buf[len] = 0;
343 			return basename(buf);
344 		}
345 	}
346 	/* path contains only '/' chars */
347 	return p;
348 }
349 #endif
350 
351 #ifndef HAVE_DIRNAME
dirname(const char * path)352 const char *dirname(const char *path)
353 {
354 	const char *p;
355 	size_t len;
356 	static char buf[1024];
357 
358 	if (path == NULL || path[0] == 0)
359 		return memcpy(buf, ".", 2);
360 
361 	/* ignore tailing '/' */
362 	len = strlen(path);
363 	while (len && path[len - 1] == '/')
364 		len--;
365 	if (!len)
366 		return memcpy(buf, "/", 2);
367 
368 	/* find end of dirname, strip '/' */
369 	if ((p = memrchr(path, '/', len)) == NULL)
370 		return memcpy(buf, ".", 2);
371 	len = p - path;
372 	while (len && path[len - 1] == '/')
373 		len--;
374 	if (!len)
375 		return memcpy(buf, "/", 2);
376 
377 	/* return it */
378 	if (len > sizeof(buf) - 1) {
379 		errno = ENAMETOOLONG;
380 		return NULL;
381 	}
382 	memcpy(buf, path, len);
383 	buf[len] = 0;
384 	return buf;
385 }
386 #endif
387 
388 #ifdef WIN32
win32_strerror(int e)389 const char *win32_strerror(int e)
390 {
391 	static char buf[1024];
392 	return strerror_r(e, buf, sizeof(buf));
393 }
394 #endif
395 
396 /* restore original strerror_r() */
397 #undef strerror_r
398 
usual_strerror_r(int e,char * dst,size_t dstlen)399 const char *usual_strerror_r(int e, char *dst, size_t dstlen)
400 {
401 #ifdef WIN32
402 	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, e,
403 		      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
404 		      dst, dstlen, NULL);
405 #else /* !WIN32 */
406 
407 #ifdef STRERROR_R_CHAR_P
408 	dst = strerror_r(e, dst, dstlen);
409 #else
410 	if (strerror_r(e, dst, dstlen) != 0)
411 		strlcpy(dst, "ERR", dstlen);
412 #endif
413 
414 #endif /* !WIN32 */
415 
416 	return dst;
417 }
418 
mempbrk(const void * data,size_t dlen,const void * find,size_t flen)419 void *mempbrk(const void *data, size_t dlen, const void *find, size_t flen)
420 {
421 	const uint8_t *s = data;
422 	const uint8_t *fb = find;
423 	size_t i;
424 	struct Bitmap256 bmap;
425 
426 	if (flen == 0)
427 		return NULL;
428 	if (flen == 1)
429 		return memchr(data, fb[0], dlen);
430 
431 	bitmap256_init(&bmap);
432 	for (i = 0; i < flen; i++)
433 		bitmap256_set(&bmap, fb[i]);
434 	for (i = 0; i < dlen; i++) {
435 		if (bitmap256_is_set(&bmap, s[i]))
436 			return (void *)(s + i);
437 	}
438 	return NULL;
439 }
440 
memspn(const void * data,size_t dlen,const void * accept,size_t alen)441 size_t memspn(const void *data, size_t dlen, const void *accept, size_t alen)
442 {
443 	const uint8_t *s = data;
444 	const uint8_t *fb = accept;
445 	size_t i;
446 	struct Bitmap256 bmap;
447 
448 	if (alen == 0)
449 		return 0;
450 	if (alen == 1) {
451 		for (i = 0; i < dlen; i++)
452 			if (s[i] != fb[0])
453 				break;
454 		return i;
455 	}
456 
457 	bitmap256_init(&bmap);
458 	for (i = 0; i < alen; i++)
459 		bitmap256_set(&bmap, fb[i]);
460 	for (i = 0; i < dlen; i++) {
461 		if (!bitmap256_is_set(&bmap, s[i]))
462 			break;
463 	}
464 	return i;
465 }
466 
memcspn(const void * data,size_t dlen,const void * reject,size_t rlen)467 size_t memcspn(const void *data, size_t dlen, const void *reject, size_t rlen)
468 {
469 	const void *p;
470 
471 	p = mempbrk(data, dlen, reject, rlen);
472 	if (p != NULL)
473 		return (char *)p - (char *)data;
474 	return dlen;
475 }
476 
strtod_dot(const char * s,char ** tokend)477 double strtod_dot(const char *s, char **tokend)
478 {
479 	const char *dp;
480 	char buf[128];
481 	char *dst, *tmp, *end, *dot = NULL;
482 	unsigned int i, dplen;
483 	double res;
484 
485 	/* check if locale is sane */
486 #ifdef HAVE_NL_LANGINFO
487 	dp = nl_langinfo(RADIXCHAR);
488 #else
489 	dp = localeconv()->decimal_point;
490 #endif
491 	if (memcmp(dp, ".", 2) == 0)
492 		return strtod(s, tokend);
493 
494 	/* try to use proper api */
495 #ifdef HAVE_STRTOD_L
496 	{
497 		static locale_t c_locale = NULL;
498 		if (!c_locale)
499 			c_locale = newlocale(LC_ALL_MASK, "C", NULL);
500 		if (c_locale)
501 			return strtod_l(s, tokend, c_locale);
502 	}
503 #endif
504 
505 	while (*s && isspace(*s))
506 		s++;
507 
508 	dot = NULL;
509 	dst = buf;
510 	end = buf + sizeof(buf) - 5;
511 	dplen = dp[1] ? strlen(dp) : 1;
512 	for (i = 0; s[i]; i++) {
513 		if (s[i] >= '0' && s[i] <= '9') {
514 			*dst++ = s[i];
515 		} else if (s[i] == '.') {
516 			dot = dst;
517 			memcpy(dst, dp, dplen);
518 			dst += dplen;
519 		} else if (s[i] == '-' || s[i] == '+' || s[i] == 'e' || s[i] == 'E') {
520 			*dst++ = s[i];
521 		} else {
522 			break;
523 		}
524 
525 		if (dst >= end) {
526 			errno = ERANGE;
527 			return 0;
528 		}
529 	}
530 	*dst = '\0';
531 
532 	if (!dot)
533 		return strtod(s, tokend);
534 
535 	tmp = NULL;
536 	res = strtod(buf, &tmp);
537 	if (tmp && tokend) {
538 		*tokend = (char *)s + (tmp - buf);
539 		if (dot && tmp > dot && dplen > 1)
540 			*tokend -= (dplen - 1);
541 	}
542 	return res;
543 }
544 
545 
dtostr_dot(char * buf,size_t buflen,double val)546 ssize_t dtostr_dot(char *buf, size_t buflen, double val)
547 {
548 	const char *dp;
549 	ssize_t len, dplen;
550 	char *p;
551 
552 	/* render with max precision */
553 	len = snprintf(buf, buflen, "%.17g", val);
554 	if (len >= (ssize_t)buflen || len < 0)
555 		return len;
556 
557 	/* check if locale is sane */
558 #ifdef HAVE_NL_LANGINFO
559 	dp = nl_langinfo(RADIXCHAR);
560 #else
561 	dp = localeconv()->decimal_point;
562 #endif
563 	if (memcmp(dp, ".", 2) == 0)
564 		return len;
565 
566 	dplen = dp[1] ? strlen(dp) : 1;
567 	p = memchr(buf, dp[0], len);
568 	if (p) {
569 		*p = '.';
570 		if (dp[1]) {
571 			memmove(p + 1, p + dplen, strlen(p + dplen) + 1);
572 			len -= dplen - 1;
573 		}
574 	}
575 	return len;
576 }
577 
578 #ifndef HAVE_STRTONUM
579 
strtonum(const char * s,long long minval,long long maxval,const char ** errstr_p)580 long long strtonum(const char *s, long long minval, long long maxval, const char **errstr_p)
581 {
582 	char *end = NULL;
583 	long long res;
584 	int old_errno = errno;
585 
586 	if (minval > maxval)
587 		goto einval;
588 
589 	errno = 0;
590 	res = strtoll(s, &end, 10);
591 	if (errno == ERANGE) {
592 		if (res < 0)
593 			goto esmall;
594 		else
595 			goto elarge;
596 	} else if (errno != 0) {
597 		goto einval;
598 	} else if (*end || end == s) {
599 		goto einval;
600 	} else if (res < minval) {
601 		goto esmall;
602 	} else if (res > maxval) {
603 		goto elarge;
604 	}
605 
606 	/* success */
607 	if (errstr_p)
608 		*errstr_p = NULL;
609 	errno = old_errno;
610 	return res;
611 
612 esmall:
613 	if (errstr_p)
614 		*errstr_p = "too small";
615 	errno = ERANGE;
616 	return 0;
617 
618 elarge:
619 	if (errstr_p)
620 		*errstr_p = "too large";
621 	errno = ERANGE;
622 	return 0;
623 
624 einval:
625 	if (errstr_p)
626 		*errstr_p = "invalid";
627 	errno = EINVAL;
628 	return 0;
629 }
630 
631 #endif
632 
633 #ifndef HAVE_STRSEP
634 
strsep(char ** stringp,const char * delim)635 char *strsep(char **stringp, const char *delim)
636 {
637 	char *end, *start = *stringp;
638 	if (start) {
639 		end = start + strcspn(start, delim);
640 		*stringp = *end ? end + 1 : NULL;
641 		*end = 0;
642 	}
643 	return start;
644 }
645 
646 #endif
647 
648 #ifndef HAVE_ASPRINTF
649 
asprintf(char ** dst_p,const char * fmt,...)650 int asprintf(char **dst_p, const char *fmt, ...)
651 {
652 	int res;
653 	va_list ap;
654 	va_start(ap, fmt);
655 	res = vasprintf(dst_p, fmt, ap);
656 	va_end(ap);
657 	return res;
658 }
659 
660 #endif
661 
662 #ifndef HAVE_VASPRINTF
663 
vasprintf(char ** dst_p,const char * fmt,va_list ap)664 int vasprintf(char **dst_p, const char *fmt, va_list ap)
665 {
666 	return cx_vasprintf(NULL, dst_p, fmt, ap);
667 }
668 
669 #endif
670 
671 #ifndef HAVE_STRNLEN
672 
strnlen(const char * string,size_t maxlen)673 size_t strnlen(const char *string, size_t maxlen)
674 {
675 	const char *end = memchr(string, '\0', maxlen);
676 	return end ? (size_t)(end - string) : maxlen;
677 }
678 
679 #endif
680