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