xref: /freebsd/lib/libbsdconf/bsdconf_string.c (revision 3fe5961a0b708da599d42cbb6b5e4f030c28ea45)
1*3fe5961aSDevin Teske /*
2*3fe5961aSDevin Teske  * Copyright (c) 2001-2026 Devin Teske <dteske@FreeBSD.org>
3*3fe5961aSDevin Teske  * Copyright (c) 2021-2026 Faraz Vahedi <kfv@FreeBSD.org>
4*3fe5961aSDevin Teske  *
5*3fe5961aSDevin Teske  * SPDX-License-Identifier: BSD-2-Clause
6*3fe5961aSDevin Teske  */
7*3fe5961aSDevin Teske 
8*3fe5961aSDevin Teske #include <ctype.h>
9*3fe5961aSDevin Teske #include <errno.h>
10*3fe5961aSDevin Teske #include <stdlib.h>
11*3fe5961aSDevin Teske #include <string.h>
12*3fe5961aSDevin Teske 
13*3fe5961aSDevin Teske #include "bsdconf_internal.h"
14*3fe5961aSDevin Teske 
15*3fe5961aSDevin Teske /*
16*3fe5961aSDevin Teske  * Counts the number of occurrences of one string that appear in the source
17*3fe5961aSDevin Teske  * string. Return value is the total count.
18*3fe5961aSDevin Teske  *
19*3fe5961aSDevin Teske  * An example use would be if you need to know how large a buffer needs to be
20*3fe5961aSDevin Teske  * for a bsdconf_replaceall() series.
21*3fe5961aSDevin Teske  */
22*3fe5961aSDevin Teske unsigned int
bsdconf_strcount(const char * source,const char * find)23*3fe5961aSDevin Teske bsdconf_strcount(const char *source, const char *find)
24*3fe5961aSDevin Teske {
25*3fe5961aSDevin Teske 	const char *p;
26*3fe5961aSDevin Teske 	size_t flen;
27*3fe5961aSDevin Teske 	unsigned int n = 0;
28*3fe5961aSDevin Teske 
29*3fe5961aSDevin Teske 	if (source == NULL || find == NULL || *source == '\0' || *find == '\0')
30*3fe5961aSDevin Teske 		return (0);
31*3fe5961aSDevin Teske 
32*3fe5961aSDevin Teske 	flen = strlen(find);
33*3fe5961aSDevin Teske 	for (p = source; (p = strstr(p, find)) != NULL; p += flen)
34*3fe5961aSDevin Teske 		n++;
35*3fe5961aSDevin Teske 
36*3fe5961aSDevin Teske 	return (n);
37*3fe5961aSDevin Teske }
38*3fe5961aSDevin Teske 
39*3fe5961aSDevin Teske /*
40*3fe5961aSDevin Teske  * Replaces all occurrences of `find' in `buf' with `replace'.
41*3fe5961aSDevin Teske  *
42*3fe5961aSDevin Teske  * `buf' must point to a mutable buffer of at least `buflen' bytes (including
43*3fe5961aSDevin Teske  * space for the terminating NUL). A string constant will not compile as the
44*3fe5961aSDevin Teske  * first argument without a cast; do not cast one in. A local or global
45*3fe5961aSDevin Teske  * non-const array is fine.
46*3fe5961aSDevin Teske  *
47*3fe5961aSDevin Teske  * The result is always built in a temporary buffer and copied back, so the
48*3fe5961aSDevin Teske  * same path is taken whether `replace' is longer or shorter than `find'.
49*3fe5961aSDevin Teske  * Pass a `buflen' large enough for the expanded result (bsdconf_strcount()
50*3fe5961aSDevin Teske  * can size it); if the result would not fit, -1 is returned with errno set
51*3fe5961aSDevin Teske  * to ENOSPC and `buf' is left unmodified. On success the return value is
52*3fe5961aSDevin Teske  * the length (in bytes) of the result, not counting the terminating NUL.
53*3fe5961aSDevin Teske  *
54*3fe5961aSDevin Teske  * When an error occurs, -1 is returned and the global variable errno is set
55*3fe5961aSDevin Teske  * accordingly.
56*3fe5961aSDevin Teske  */
57*3fe5961aSDevin Teske int
bsdconf_replaceall(char * buf,size_t buflen,const char * find,const char * replace)58*3fe5961aSDevin Teske bsdconf_replaceall(char *buf, size_t buflen, const char *find,
59*3fe5961aSDevin Teske     const char *replace)
60*3fe5961aSDevin Teske {
61*3fe5961aSDevin Teske 	char *dst;
62*3fe5961aSDevin Teske 	char *out;
63*3fe5961aSDevin Teske 	const char *hit;
64*3fe5961aSDevin Teske 	const char *src;
65*3fe5961aSDevin Teske 	size_t flen, need, rlen, slen;
66*3fe5961aSDevin Teske 	unsigned int n;
67*3fe5961aSDevin Teske 
68*3fe5961aSDevin Teske 	if (buf == NULL)
69*3fe5961aSDevin Teske 		return (0);
70*3fe5961aSDevin Teske 	if (find == NULL)
71*3fe5961aSDevin Teske 		return ((int)strlen(buf));
72*3fe5961aSDevin Teske 
73*3fe5961aSDevin Teske 	slen = strlen(buf);
74*3fe5961aSDevin Teske 	flen = strlen(find);
75*3fe5961aSDevin Teske 	rlen = replace != NULL ? strlen(replace) : 0;
76*3fe5961aSDevin Teske 
77*3fe5961aSDevin Teske 	if (slen == 0 || flen == 0 || slen < flen)
78*3fe5961aSDevin Teske 		return ((int)slen);
79*3fe5961aSDevin Teske 
80*3fe5961aSDevin Teske 	n = bsdconf_strcount(buf, find);
81*3fe5961aSDevin Teske 	if (n == 0)
82*3fe5961aSDevin Teske 		return ((int)slen);
83*3fe5961aSDevin Teske 
84*3fe5961aSDevin Teske 	if (rlen >= flen)
85*3fe5961aSDevin Teske 		need = slen + (size_t)n * (rlen - flen) + 1;
86*3fe5961aSDevin Teske 	else
87*3fe5961aSDevin Teske 		need = slen - (size_t)n * (flen - rlen) + 1;
88*3fe5961aSDevin Teske 	if (need > buflen) {
89*3fe5961aSDevin Teske 		errno = ENOSPC;
90*3fe5961aSDevin Teske 		return (-1);
91*3fe5961aSDevin Teske 	}
92*3fe5961aSDevin Teske 
93*3fe5961aSDevin Teske 	if ((out = malloc(need)) == NULL)
94*3fe5961aSDevin Teske 		return (-1);
95*3fe5961aSDevin Teske 
96*3fe5961aSDevin Teske 	dst = out;
97*3fe5961aSDevin Teske 	src = buf;
98*3fe5961aSDevin Teske 	while ((hit = strstr(src, find)) != NULL) {
99*3fe5961aSDevin Teske 		memcpy(dst, src, (size_t)(hit - src));
100*3fe5961aSDevin Teske 		dst += hit - src;
101*3fe5961aSDevin Teske 		if (rlen > 0) {
102*3fe5961aSDevin Teske 			memcpy(dst, replace, rlen);
103*3fe5961aSDevin Teske 			dst += rlen;
104*3fe5961aSDevin Teske 		}
105*3fe5961aSDevin Teske 		src = hit + flen;
106*3fe5961aSDevin Teske 	}
107*3fe5961aSDevin Teske 	memcpy(dst, src, strlen(src) + 1);
108*3fe5961aSDevin Teske 	memcpy(buf, out, need);
109*3fe5961aSDevin Teske 	free(out);
110*3fe5961aSDevin Teske 
111*3fe5961aSDevin Teske 	return ((int)(need - 1));
112*3fe5961aSDevin Teske }
113*3fe5961aSDevin Teske 
114*3fe5961aSDevin Teske /*
115*3fe5961aSDevin Teske  * Unexpands (collapses) C-style escape sequences in `src' into `dst'.
116*3fe5961aSDevin Teske  *
117*3fe5961aSDevin Teske  * The result is never longer than the input, so `dst' may be the same buffer
118*3fe5961aSDevin Teske  * as `src' (cf. strunvis(3)). Do not pass a string constant as `dst'; a local
119*3fe5961aSDevin Teske  * or global non-const array is fine.
120*3fe5961aSDevin Teske  *
121*3fe5961aSDevin Teske  * Interpreted sequences are:
122*3fe5961aSDevin Teske  *
123*3fe5961aSDevin Teske  * 	\NNN	character with octal value NNN (1 to 3 digits)
124*3fe5961aSDevin Teske  * 	\a	alert (BEL)
125*3fe5961aSDevin Teske  * 	\b	backspace
126*3fe5961aSDevin Teske  * 	\f	form feed
127*3fe5961aSDevin Teske  * 	\n	new line
128*3fe5961aSDevin Teske  * 	\r	carriage return
129*3fe5961aSDevin Teske  * 	\t	horizontal tab
130*3fe5961aSDevin Teske  * 	\v	vertical tab
131*3fe5961aSDevin Teske  * 	\xNN	byte with hexadecimal value NN (1 to 2 digits)
132*3fe5961aSDevin Teske  *
133*3fe5961aSDevin Teske  * All other sequences are unescaped (ie. '\"' and '\#'). A trailing backslash
134*3fe5961aSDevin Teske  * or a `\x' with no following hex digits is emitted verbatim (the backslash
135*3fe5961aSDevin Teske  * is dropped for `\x', leaving `x').
136*3fe5961aSDevin Teske  */
137*3fe5961aSDevin Teske void
bsdconf_strunexpand(char * dst,const char * src)138*3fe5961aSDevin Teske bsdconf_strunexpand(char *dst, const char *src)
139*3fe5961aSDevin Teske {
140*3fe5961aSDevin Teske 	char *d;
141*3fe5961aSDevin Teske 	const char *s;
142*3fe5961aSDevin Teske 	unsigned int n, v;
143*3fe5961aSDevin Teske 	unsigned char c;
144*3fe5961aSDevin Teske 
145*3fe5961aSDevin Teske 	d = dst;
146*3fe5961aSDevin Teske 	s = src;
147*3fe5961aSDevin Teske 
148*3fe5961aSDevin Teske 	/*
149*3fe5961aSDevin Teske 	 * Loop until we hit the end of the input. The stop condition must
150*3fe5961aSDevin Teske 	 * track the input cursor (s): collapsing an escape advances s ahead
151*3fe5961aSDevin Teske 	 * of the output cursor (d), so once the two diverge *d no longer
152*3fe5961aSDevin Teske 	 * reflects where the input terminates.
153*3fe5961aSDevin Teske 	 */
154*3fe5961aSDevin Teske 	while (*s != '\0') {
155*3fe5961aSDevin Teske 		if (*s != '\\') {
156*3fe5961aSDevin Teske 			*d++ = *s++;
157*3fe5961aSDevin Teske 			continue;
158*3fe5961aSDevin Teske 		}
159*3fe5961aSDevin Teske 
160*3fe5961aSDevin Teske 		/*
161*3fe5961aSDevin Teske 		 * A backslash at the very end of the string escapes nothing
162*3fe5961aSDevin Teske 		 * (there is no next character); emit it verbatim and stop
163*3fe5961aSDevin Teske 		 * rather than read past the terminator.
164*3fe5961aSDevin Teske 		 */
165*3fe5961aSDevin Teske 		if (*(s + 1) == '\0') {
166*3fe5961aSDevin Teske 			*d++ = *s++;
167*3fe5961aSDevin Teske 			break;
168*3fe5961aSDevin Teske 		}
169*3fe5961aSDevin Teske 
170*3fe5961aSDevin Teske 		/* Replace the backslash with the correct character */
171*3fe5961aSDevin Teske 		s++;
172*3fe5961aSDevin Teske 		switch (*s) {
173*3fe5961aSDevin Teske 		case 'a': *d = '\a'; break; /* bell/alert (BEL) */
174*3fe5961aSDevin Teske 		case 'b': *d = '\b'; break; /* backspace */
175*3fe5961aSDevin Teske 		case 'f': *d = '\f'; break; /* form feed */
176*3fe5961aSDevin Teske 		case 'n': *d = '\n'; break; /* new line */
177*3fe5961aSDevin Teske 		case 'r': *d = '\r'; break; /* carriage return */
178*3fe5961aSDevin Teske 		case 't': *d = '\t'; break; /* horizontal tab */
179*3fe5961aSDevin Teske 		case 'v': *d = '\v'; break; /* vertical tab */
180*3fe5961aSDevin Teske 		case 'x': /* hex value (1 to 2 digits)(\xNN) */
181*3fe5961aSDevin Teske 			v = 0;
182*3fe5961aSDevin Teske 			n = 0;
183*3fe5961aSDevin Teske 			while (n < 2) {
184*3fe5961aSDevin Teske 				c = (unsigned char)*(s + 1);
185*3fe5961aSDevin Teske 				if (c >= '0' && c <= '9')
186*3fe5961aSDevin Teske 					v = (v << 4) + (c - '0');
187*3fe5961aSDevin Teske 				else if (c >= 'A' && c <= 'F')
188*3fe5961aSDevin Teske 					v = (v << 4) + (c - 'A' + 10);
189*3fe5961aSDevin Teske 				else if (c >= 'a' && c <= 'f')
190*3fe5961aSDevin Teske 					v = (v << 4) + (c - 'a' + 10);
191*3fe5961aSDevin Teske 				else
192*3fe5961aSDevin Teske 					break;
193*3fe5961aSDevin Teske 				s++;
194*3fe5961aSDevin Teske 				n++;
195*3fe5961aSDevin Teske 			}
196*3fe5961aSDevin Teske 			/* \x with no digits: emit the 'x' (unknown escape) */
197*3fe5961aSDevin Teske 			*d = (n == 0) ? 'x' : (char)v;
198*3fe5961aSDevin Teske 			break;
199*3fe5961aSDevin Teske 		default: /* octal (\NNN, 1 to 3 digits) or unknown sequence */
200*3fe5961aSDevin Teske 			if (*s >= '0' && *s <= '7') {
201*3fe5961aSDevin Teske 				v = (unsigned int)(*s - '0');
202*3fe5961aSDevin Teske 				n = 1;
203*3fe5961aSDevin Teske 				while (n < 3 && *(s + 1) >= '0' &&
204*3fe5961aSDevin Teske 				    *(s + 1) <= '7') {
205*3fe5961aSDevin Teske 					s++;
206*3fe5961aSDevin Teske 					v = (v << 3) +
207*3fe5961aSDevin Teske 					    (unsigned int)(*s - '0');
208*3fe5961aSDevin Teske 					n++;
209*3fe5961aSDevin Teske 				}
210*3fe5961aSDevin Teske 				*d = (char)v;
211*3fe5961aSDevin Teske 			} else
212*3fe5961aSDevin Teske 				*d = *s;
213*3fe5961aSDevin Teske 			break;
214*3fe5961aSDevin Teske 		}
215*3fe5961aSDevin Teske 
216*3fe5961aSDevin Teske 		/* Increment to next offset, possible next escape sequence */
217*3fe5961aSDevin Teske 		d++;
218*3fe5961aSDevin Teske 		s++;
219*3fe5961aSDevin Teske 	}
220*3fe5961aSDevin Teske 
221*3fe5961aSDevin Teske 	/*
222*3fe5961aSDevin Teske 	 * Terminate at the (possibly earlier) output cursor. When any
223*3fe5961aSDevin Teske 	 * escape was collapsed the string shrank, so the trailing bytes
224*3fe5961aSDevin Teske 	 * between d and s are now stale and must be cut off here.
225*3fe5961aSDevin Teske 	 */
226*3fe5961aSDevin Teske 	*d = '\0';
227*3fe5961aSDevin Teske }
228*3fe5961aSDevin Teske 
229*3fe5961aSDevin Teske /*
230*3fe5961aSDevin Teske  * Convert a string to lower case. Pass a mutable buffer (a local or global
231*3fe5961aSDevin Teske  * non-const array is fine); do not pass a string constant.
232*3fe5961aSDevin Teske  */
233*3fe5961aSDevin Teske void
bsdconf_strtolower(char * buf)234*3fe5961aSDevin Teske bsdconf_strtolower(char *buf)
235*3fe5961aSDevin Teske {
236*3fe5961aSDevin Teske 	char *p = buf;
237*3fe5961aSDevin Teske 
238*3fe5961aSDevin Teske 	if (buf == NULL)
239*3fe5961aSDevin Teske 		return;
240*3fe5961aSDevin Teske 
241*3fe5961aSDevin Teske 	while (*p != '\0') {
242*3fe5961aSDevin Teske 		*p = (char)tolower((unsigned char)*p);
243*3fe5961aSDevin Teske 		p++;
244*3fe5961aSDevin Teske 	}
245*3fe5961aSDevin Teske }
246