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