/src/freeradius-server/src/lib/util/sbuff.c
Line | Count | Source |
1 | | /* |
2 | | * This library is free software; you can redistribute it and/or |
3 | | * modify it under the terms of the GNU Lesser General Public |
4 | | * License as published by the Free Software Foundation; either |
5 | | * version 2.1 of the License, or (at your option) any later version. |
6 | | * |
7 | | * This library is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
10 | | * Lesser General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU Lesser General Public |
13 | | * License along with this library; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** A generic string buffer structure for string printing and parsing |
18 | | * |
19 | | * @file src/lib/util/sbuff.c |
20 | | * |
21 | | * @copyright 2020 Arran Cudbard-Bell <a.cudbardb@freeradius.org> |
22 | | */ |
23 | | RCSID("$Id: 6e6cb8fe09bf95a937c78f8ff09b42ab65de2653 $") |
24 | | |
25 | | #include <freeradius-devel/util/misc.h> |
26 | | #include <freeradius-devel/util/syserror.h> |
27 | | #include <freeradius-devel/util/atexit.h> |
28 | | |
29 | | |
30 | | static _Thread_local char *sbuff_scratch; |
31 | | |
32 | | /** When true, prevent use of the scratch space |
33 | | * |
34 | | * This prevents us from initialising a pool after the thread local destructors have run. |
35 | | * |
36 | | * The destructors may be called manually before thread exit, and we don't want to re-initialise the pool |
37 | | */ |
38 | | static _Thread_local bool sbuff_scratch_freed; |
39 | | |
40 | | static_assert(sizeof(long long) >= sizeof(int64_t), "long long must be as wide or wider than an int64_t"); |
41 | | static_assert(sizeof(unsigned long long) >= sizeof(uint64_t), "long long must be as wide or wider than an uint64_t"); |
42 | | |
43 | | fr_table_num_ordered_t const sbuff_parse_error_table[] = { |
44 | | { L("ok"), FR_SBUFF_PARSE_OK }, |
45 | | { L("token not found"), FR_SBUFF_PARSE_ERROR_NOT_FOUND }, |
46 | | { L("trailing data"), FR_SBUFF_PARSE_ERROR_TRAILING }, |
47 | | { L("token format invalid"), FR_SBUFF_PARSE_ERROR_FORMAT }, |
48 | | { L("out of space"), FR_SBUFF_PARSE_ERROR_OUT_OF_SPACE }, |
49 | | { L("integer overflow"), FR_SBUFF_PARSE_ERROR_NUM_OVERFLOW }, |
50 | | { L("integer underflow"), FR_SBUFF_PARSE_ERROR_NUM_UNDERFLOW }, |
51 | | { L("empty input is invalid"), FR_SBUFF_PARSE_ERROR_INPUT_EMPTY }, |
52 | | }; |
53 | | size_t sbuff_parse_error_table_len = NUM_ELEMENTS(sbuff_parse_error_table); |
54 | | |
55 | | #if defined(STATIC_ANALYZER) || !defined(NDEBUG) |
56 | 85.3M | # define CHECK_SBUFF_INIT(_sbuff) do { if (!(_sbuff)->extend && (unlikely(!(_sbuff)->buff) || unlikely(!(_sbuff)->start) || unlikely(!(_sbuff)->end) || unlikely(!(_sbuff)->p))) return 0; } while (0) |
57 | 84.4M | # define CHECK_SBUFF_WRITEABLE(_sbuff) do { CHECK_SBUFF_INIT(_sbuff); if (unlikely((_sbuff)->is_const)) return 0; } while (0) |
58 | | |
59 | | #else |
60 | | # define CHECK_SBUFF_INIT(_sbuff) |
61 | | # define CHECK_SBUFF_WRITEABLE(_sbuff) |
62 | | #endif |
63 | | |
64 | | bool const sbuff_char_class_uint[SBUFF_CHAR_CLASS] = { |
65 | | SBUFF_CHAR_CLASS_NUM, |
66 | | ['+'] = true |
67 | | }; |
68 | | |
69 | | bool const sbuff_char_class_int[SBUFF_CHAR_CLASS] = { |
70 | | SBUFF_CHAR_CLASS_NUM, |
71 | | ['+'] = true, ['-'] = true |
72 | | }; |
73 | | |
74 | | bool const sbuff_char_class_float[SBUFF_CHAR_CLASS] = { |
75 | | SBUFF_CHAR_CLASS_NUM, |
76 | | ['-'] = true, ['+'] = true, ['e'] = true, ['E'] = true, ['.'] = true, |
77 | | }; |
78 | | |
79 | | bool const sbuff_char_class_zero[SBUFF_CHAR_CLASS] = { |
80 | | ['0'] = true |
81 | | }; |
82 | | |
83 | | /* |
84 | | * Anything which vaguely resembles an IP address, prefix, or host name. |
85 | | */ |
86 | | bool const sbuff_char_class_hostname[SBUFF_CHAR_CLASS] = { |
87 | | SBUFF_CHAR_CLASS_ALPHA_NUM, |
88 | | ['.'] = true, /* only for IPv4 and host names */ |
89 | | [':'] = true, /* only for IPv6 numerical addresses */ |
90 | | ['-'] = true, /* only for host names */ |
91 | | ['/'] = true, /* only for prefixes */ |
92 | | ['['] = true, /* only for IPv6 numerical addresses */ |
93 | | [']'] = true, /* only for IPv6 numerical addresses */ |
94 | | ['_'] = true, /* only for certain host name labels */ |
95 | | ['*'] = true, /* really only for ipv4 addresses */ |
96 | | }; |
97 | | |
98 | | bool const sbuff_char_class_hex[SBUFF_CHAR_CLASS] = { SBUFF_CHAR_CLASS_HEX }; |
99 | | bool const sbuff_char_alpha_num[SBUFF_CHAR_CLASS] = { SBUFF_CHAR_CLASS_ALPHA_NUM }; |
100 | | bool const sbuff_char_word[SBUFF_CHAR_CLASS] = { |
101 | | SBUFF_CHAR_CLASS_ALPHA_NUM, |
102 | | ['-'] = true, ['_'] = true, |
103 | | }; |
104 | | bool const sbuff_char_whitespace[SBUFF_CHAR_CLASS] = { |
105 | | ['\t'] = true, ['\n'] = true, ['\r'] = true, ['\f'] = true, ['\v'] = true, [' '] = true, |
106 | | }; |
107 | | |
108 | | bool const sbuff_char_line_endings[SBUFF_CHAR_CLASS] = { |
109 | | ['\n'] = true, ['\r'] = true |
110 | | }; |
111 | | |
112 | | bool const sbuff_char_blank[SBUFF_CHAR_CLASS] = { |
113 | | ['\t'] = true, [' '] = true, |
114 | | }; |
115 | | |
116 | | /** Copy function that allows overlapping memory ranges to be copied |
117 | | * |
118 | | * @param[out] o_start start of output buffer. |
119 | | * @param[in] o_end end of the output buffer. |
120 | | * @param[in] i_start start of the input buffer. |
121 | | * @param[in] i_end end of data to copy. |
122 | | * @return |
123 | | * - >0 the number of bytes copied. |
124 | | * - 0 invalid args. |
125 | | * - <0 the number of bytes we'd need to complete the copy. |
126 | | */ |
127 | | static inline CC_HINT(always_inline) ssize_t safecpy(char *o_start, char *o_end, |
128 | | char const *i_start, char const *i_end) |
129 | 42.0M | { |
130 | 42.0M | ssize_t diff; |
131 | 42.0M | size_t i_len = i_end - i_start; |
132 | | |
133 | 42.0M | if (unlikely((o_end < o_start) || (i_end < i_start))) return 0; /* sanity check */ |
134 | | |
135 | 42.0M | diff = (o_end - o_start) - (i_len); |
136 | 42.0M | if (diff < 0) return diff; |
137 | | |
138 | 42.0M | if ((i_start > o_end) || (i_end < o_start)) { /* no-overlap */ |
139 | 42.0M | memcpy(o_start, i_start, i_len); |
140 | 42.0M | } else { /* overlap */ |
141 | 0 | memmove(o_start, i_start, i_len); |
142 | 0 | } |
143 | | |
144 | 42.0M | return (i_len); |
145 | 42.0M | } |
146 | | |
147 | | static inline CC_HINT(always_inline) size_t min(size_t x, size_t y) |
148 | 373k | { |
149 | 373k | return x < y ? x : y; |
150 | 373k | } |
151 | | |
152 | | /** Update all markers and pointers in the set of sbuffs to point to new_buff |
153 | | * |
154 | | * This function should be used if the underlying buffer is realloced. |
155 | | * |
156 | | * @param[in] sbuff to update. |
157 | | * @param[in] new_buff to assign to to sbuff. |
158 | | * @param[in] new_len Length of the new buffer. |
159 | | */ |
160 | | void fr_sbuff_update(fr_sbuff_t *sbuff, char *new_buff, size_t new_len) |
161 | 102k | { |
162 | 102k | fr_sbuff_t *sbuff_i; |
163 | 102k | char *old_buff; /* Current buff */ |
164 | | |
165 | 102k | old_buff = sbuff->buff; |
166 | | |
167 | | /* |
168 | | * Update pointers to point to positions |
169 | | * in new buffer based on their relative |
170 | | * offsets in the old buffer... but not |
171 | | * past the end of the new buffer. |
172 | | */ |
173 | 288k | for (sbuff_i = sbuff; sbuff_i; sbuff_i = sbuff_i->parent) { |
174 | 186k | fr_sbuff_marker_t *m_i; |
175 | | |
176 | 186k | sbuff_i->buff = new_buff; |
177 | 186k | sbuff_i->start = new_buff + min(new_len, sbuff_i->start - old_buff); |
178 | 186k | sbuff_i->end = sbuff_i->buff + new_len; |
179 | 186k | *(sbuff_i->end) = '\0'; /* Re-terminate */ |
180 | | |
181 | 186k | sbuff_i->p = new_buff + min(new_len, sbuff_i->p - old_buff); |
182 | | |
183 | 186k | for (m_i = sbuff_i->m; m_i; m_i = m_i->next) m_i->p = new_buff + min(new_len, m_i->p - old_buff); |
184 | 186k | } |
185 | 102k | } |
186 | | |
187 | | /** Shift the contents of the sbuff, returning the number of bytes we managed to shift |
188 | | * |
189 | | * @param[in] sbuff to shift. |
190 | | * @param[in] shift the contents of the buffer this many bytes |
191 | | * towards the start of the buffer. |
192 | | * @param[in] move_end If the buffer is used for reading, then this should be true |
193 | | * so we cannot read passed the end of valid data. |
194 | | * @return |
195 | | * - 0 the shift failed due to constraining pointers. |
196 | | * - >0 the number of bytes we managed to shift pointers |
197 | | * in the sbuff. memmove should be used to move the |
198 | | * existing contents of the buffer, and fill the free |
199 | | * space at the end of the buffer with additional data. |
200 | | */ |
201 | | size_t fr_sbuff_shift(fr_sbuff_t *sbuff, size_t shift, bool move_end) |
202 | 0 | { |
203 | 0 | fr_sbuff_t *sbuff_i; |
204 | 0 | char *buff, *end; /* Current start */ |
205 | 0 | size_t max_shift = shift; |
206 | 0 | bool reterminate = false; |
207 | |
|
208 | 0 | CHECK_SBUFF_INIT(sbuff); |
209 | | |
210 | 0 | buff = sbuff->buff; |
211 | 0 | end = sbuff->end; |
212 | | |
213 | | /* |
214 | | * If the sbuff is already \0 terminated |
215 | | * and we're not working on a const buffer |
216 | | * then assume we need to re-terminate |
217 | | * later. |
218 | | */ |
219 | 0 | reterminate = (sbuff->p < sbuff->end) && (*sbuff->p == '\0') && !sbuff->is_const; |
220 | | |
221 | | /* |
222 | | * First pass: find the maximum shift, which is the minimum |
223 | | * of the distances from buff to any of the current pointers |
224 | | * or current pointers of markers of dbuff and its ancestors. |
225 | | * (We're also constrained by the requested shift count.) |
226 | | */ |
227 | 0 | for (sbuff_i = sbuff; sbuff_i; sbuff_i = sbuff_i->parent) { |
228 | 0 | fr_sbuff_marker_t *m_i; |
229 | |
|
230 | 0 | max_shift = min(max_shift, sbuff_i->p - buff); |
231 | 0 | if (!max_shift) return 0; |
232 | | |
233 | 0 | for (m_i = sbuff_i->m; m_i; m_i = m_i->next) { |
234 | 0 | max_shift = min(max_shift, m_i->p - buff); |
235 | 0 | if (!max_shift) return 0; |
236 | 0 | } |
237 | 0 | } |
238 | | |
239 | | /* |
240 | | * Second pass: adjust pointers. |
241 | | * The first pass means we need only subtract shift from |
242 | | * current pointers. Start pointers can't constrain shift, |
243 | | * or we'd never free any space, so they require the added |
244 | | * check. |
245 | | */ |
246 | 0 | for (sbuff_i = sbuff; sbuff_i; sbuff_i = sbuff_i->parent) { |
247 | 0 | fr_sbuff_marker_t *m_i; |
248 | 0 | char *start = sbuff_i->start; |
249 | |
|
250 | 0 | sbuff_i->start -= min(max_shift, sbuff_i->start - buff); |
251 | 0 | sbuff_i->p -= max_shift; |
252 | 0 | if (move_end) sbuff_i->end -= max_shift; |
253 | 0 | sbuff_i->shifted += (max_shift - (start - sbuff_i->start)); |
254 | 0 | for (m_i = sbuff_i->m; m_i; m_i = m_i->next) m_i->p -= max_shift; |
255 | 0 | } |
256 | | |
257 | | /* |
258 | | * Only memmove if the shift wasn't the |
259 | | * entire contents of the buffer. |
260 | | */ |
261 | 0 | if ((buff + max_shift) < end) memmove(buff, buff + max_shift, end - (buff + max_shift)); |
262 | |
|
263 | 0 | if (reterminate) *sbuff->p = '\0'; |
264 | |
|
265 | 0 | return max_shift; |
266 | 0 | } |
267 | | |
268 | | /** Refresh the buffer with more data from the file |
269 | | * |
270 | | */ |
271 | | size_t fr_sbuff_extend_file(fr_sbuff_extend_status_t *status, fr_sbuff_t *sbuff, size_t extension) |
272 | 0 | { |
273 | 0 | fr_sbuff_t *sbuff_i; |
274 | 0 | size_t read, available, total_read, shift; |
275 | 0 | fr_sbuff_uctx_file_t *fctx; |
276 | |
|
277 | 0 | CHECK_SBUFF_INIT(sbuff); |
278 | | |
279 | 0 | fctx = sbuff->uctx; |
280 | 0 | if (fctx->eof) return 0; |
281 | | |
282 | 0 | if (extension == SIZE_MAX) extension = 0; |
283 | |
|
284 | 0 | total_read = fctx->shifted + (sbuff->end - sbuff->buff); |
285 | 0 | if (total_read >= fctx->max) { |
286 | 0 | fr_strerror_const("Can't satisfy extension request, max bytes read"); |
287 | 0 | return 0; /* There's no way we could satisfy the extension request */ |
288 | 0 | } |
289 | | |
290 | | /* |
291 | | * Shift out the maximum number of bytes we can |
292 | | * irrespective of the amount that was requested |
293 | | * as the extension. It's more efficient to do |
294 | | * this than lots of small shifts, and just |
295 | | * looking and the number of bytes used in the |
296 | | * deepest sbuff, and using that as the shift |
297 | | * amount, might mean we don't shift anything at |
298 | | * all! |
299 | | * |
300 | | * fr_sbuff_shift will cap the max shift amount, |
301 | | * so markers and positions will remain valid for |
302 | | * all sbuffs in the chain. |
303 | | */ |
304 | 0 | shift = fr_sbuff_current(sbuff) - fr_sbuff_buff(sbuff); |
305 | 0 | if (shift) { |
306 | | /* |
307 | | * Try and shift as much as we can out |
308 | | * of the buffer to make space. |
309 | | * |
310 | | * Note: p and markers are constraints here. |
311 | | */ |
312 | 0 | fctx->shifted += fr_sbuff_shift(sbuff, shift, true); |
313 | 0 | } |
314 | |
|
315 | 0 | available = fctx->buff_end - sbuff->end; |
316 | 0 | if (available > (fctx->max - total_read)) available = fctx->max - total_read; |
317 | 0 | if (available < extension) { |
318 | 0 | fr_strerror_printf("Can't satisfy extension request for %zu bytes", extension); |
319 | 0 | return 0; /* There's no way we could satisfy the extension request */ |
320 | 0 | } |
321 | | |
322 | 0 | read = fread(sbuff->end, 1, available, fctx->file); |
323 | 0 | for (sbuff_i = sbuff; sbuff_i; sbuff_i = sbuff_i->parent) { |
324 | 0 | sbuff_i->end += read; /* Advance end, which increases fr_sbuff_remaining() */ |
325 | 0 | } |
326 | | |
327 | | /** Check for errors |
328 | | */ |
329 | 0 | if (read < available) { |
330 | 0 | if (!feof(fctx->file)) { |
331 | | /* |
332 | | * It's an error, but ferror() returns a ??? error number, |
333 | | * and not errno. |
334 | | * |
335 | | * Posix says "The ferror() function shall not change the setting of errno if |
336 | | * stream is valid". And the return value is defined to be non-zero, but with no |
337 | | * meaning associated with any non-zero values. |
338 | | */ |
339 | 0 | fr_strerror_printf("Error extending buffer: %d", ferror(fctx->file)); |
340 | 0 | *status |= FR_SBUFF_FLAG_EXTEND_ERROR; |
341 | 0 | return 0; |
342 | 0 | } |
343 | | |
344 | 0 | fctx->eof = true; |
345 | 0 | } |
346 | | |
347 | 0 | return read; |
348 | 0 | } |
349 | | |
350 | | /** Accessor function for the EOF state of the file extendor |
351 | | * |
352 | | */ |
353 | | bool fr_sbuff_eof_file(fr_sbuff_t *sbuff) |
354 | 0 | { |
355 | 0 | fr_sbuff_uctx_file_t *fctx = sbuff->uctx; |
356 | 0 | return fctx->eof; |
357 | 0 | } |
358 | | |
359 | | /** Reallocate the current buffer |
360 | | * |
361 | | * @param[in] status Extend status. |
362 | | * @param[in] sbuff to be extended. |
363 | | * @param[in] extension How many additional bytes should be allocated |
364 | | * in the buffer. |
365 | | * @return |
366 | | * - 0 the extension operation failed. |
367 | | * - >0 the number of bytes the buffer was extended by. |
368 | | */ |
369 | | size_t fr_sbuff_extend_talloc(fr_sbuff_extend_status_t *status, fr_sbuff_t *sbuff, size_t extension) |
370 | 44.7k | { |
371 | 44.7k | fr_sbuff_uctx_talloc_t *tctx = sbuff->uctx; |
372 | 44.7k | size_t clen, nlen, elen = extension; |
373 | 44.7k | char *new_buff; |
374 | | |
375 | 44.7k | CHECK_SBUFF_INIT(sbuff); |
376 | | |
377 | 44.7k | clen = sbuff->buff ? talloc_array_length(sbuff->buff) : 0; |
378 | | /* |
379 | | * If the current buffer size + the extension |
380 | | * is less than init, extend the buffer to init. |
381 | | * |
382 | | * This can happen if the buffer has been |
383 | | * trimmed, and then additional data is added. |
384 | | */ |
385 | 44.7k | if ((clen + elen) < tctx->init) { |
386 | 0 | elen = (tctx->init - clen) + 1; /* add \0 */ |
387 | | /* |
388 | | * Double the buffer size if it's more than the |
389 | | * requested amount. |
390 | | */ |
391 | 44.7k | } else if (elen < clen) { |
392 | 31.7k | elen = clen - 1; /* Don't double alloc \0 */ |
393 | 31.7k | } |
394 | | |
395 | | /* |
396 | | * Check we don't exceed the maximum buffer |
397 | | * length, including the NUL byte. |
398 | | */ |
399 | 44.7k | if (tctx->max && ((clen + elen + 1) > tctx->max)) { |
400 | 33 | elen = tctx->max - clen; |
401 | 33 | if (elen == 0) { |
402 | 0 | fr_strerror_printf("Failed extending buffer by %zu bytes to " |
403 | 0 | "%zu bytes, max is %zu bytes", |
404 | 0 | extension, clen + extension, tctx->max); |
405 | 0 | return 0; |
406 | 0 | } |
407 | 33 | elen += 1; /* add \0 */ |
408 | 33 | } |
409 | 44.7k | nlen = clen + elen; |
410 | | |
411 | 44.7k | new_buff = talloc_realloc(tctx->ctx, sbuff->buff, char, nlen); |
412 | 44.7k | if (unlikely(!new_buff)) { |
413 | 0 | fr_strerror_printf("Failed extending buffer by %zu bytes to %zu bytes", elen, nlen); |
414 | 0 | *status |= FR_SBUFF_FLAG_EXTEND_ERROR; |
415 | 0 | return 0; |
416 | 0 | } |
417 | | |
418 | 44.7k | (void)fr_sbuff_update(sbuff, new_buff, nlen - 1); /* Shouldn't fail as we're extending */ |
419 | | |
420 | 44.7k | return elen; |
421 | 44.7k | } |
422 | | |
423 | | /** Trim a talloced sbuff to the minimum length required to represent the contained string |
424 | | * |
425 | | * @param[in] sbuff to trim. |
426 | | * @param[in] len Length to trim to. Passing SIZE_MAX will |
427 | | * result in the buffer being trimmed to the |
428 | | * length of the content. |
429 | | * @return |
430 | | * - 0 on success. |
431 | | * - -1 on failure - markers present pointing past the end of string data. |
432 | | */ |
433 | | int fr_sbuff_trim_talloc(fr_sbuff_t *sbuff, size_t len) |
434 | 47.8k | { |
435 | 47.8k | size_t clen = 0, nlen = 1; |
436 | 47.8k | char *new_buff; |
437 | 47.8k | fr_sbuff_uctx_talloc_t *tctx = sbuff->uctx; |
438 | | |
439 | 47.8k | CHECK_SBUFF_INIT(sbuff); |
440 | | |
441 | 47.8k | if (sbuff->buff) clen = talloc_array_length(sbuff->buff); |
442 | | |
443 | 47.8k | if (len != SIZE_MAX) { |
444 | 0 | nlen += len; |
445 | 47.8k | } else if (sbuff->buff){ |
446 | 47.8k | nlen += (sbuff->p - sbuff->start); |
447 | 47.8k | } |
448 | | |
449 | 47.8k | if (nlen != clen) { |
450 | 42.0k | new_buff = talloc_realloc(tctx->ctx, sbuff->buff, char, nlen); |
451 | 42.0k | if (unlikely(!new_buff)) { |
452 | 0 | fr_strerror_printf("Failed trimming buffer from %zu to %zu", clen, nlen); |
453 | 0 | return -1; |
454 | 0 | } |
455 | 42.0k | fr_sbuff_update(sbuff, new_buff, nlen - 1); |
456 | 42.0k | } |
457 | | |
458 | 47.8k | return 0; |
459 | 47.8k | } |
460 | | |
461 | | /** Reset a talloced buffer to its initial length, clearing any data stored |
462 | | * |
463 | | * @param[in] sbuff to reset. |
464 | | * @return |
465 | | * - 0 on success. |
466 | | * - -1 on failure - markers present pointing past the end of string data. |
467 | | */ |
468 | | int fr_sbuff_reset_talloc(fr_sbuff_t *sbuff) |
469 | 15.5k | { |
470 | 15.5k | fr_sbuff_uctx_talloc_t *tctx = sbuff->uctx; |
471 | | |
472 | 15.5k | CHECK_SBUFF_INIT(sbuff); |
473 | | |
474 | 15.5k | fr_sbuff_set_to_start(sbuff); /* Clear data */ |
475 | 15.5k | sbuff->m = NULL; /* Remove any maker references */ |
476 | | |
477 | 15.5k | if (fr_sbuff_used(sbuff) != tctx->init) { |
478 | 15.5k | char *new_buff; |
479 | | |
480 | 15.5k | new_buff = talloc_realloc(tctx->ctx, sbuff->buff, char, tctx->init); |
481 | 15.5k | if (!new_buff) { |
482 | 0 | fr_strerror_printf("Failed reallocing from %zu to %zu", |
483 | 0 | talloc_array_length(sbuff->buff), tctx->init); |
484 | 0 | return -1; |
485 | 0 | } |
486 | 15.5k | sbuff->buff = new_buff; |
487 | 15.5k | fr_sbuff_update(sbuff, new_buff, tctx->init - 1); |
488 | 15.5k | } |
489 | | |
490 | 15.5k | return 0; |
491 | 15.5k | } |
492 | | |
493 | | /** Fill as much of the output buffer we can and break on partial copy |
494 | | * |
495 | | * @param[in] _out sbuff to write to. |
496 | | * @param[in] _in sbuff to copy from. |
497 | | * @param[in] _len maximum amount to copy. |
498 | | */ |
499 | 210k | #define FILL_OR_GOTO_DONE(_out, _in, _len) if (fr_sbuff_move(_out, _in, _len) < (size_t)(_len)) goto done |
500 | | |
501 | | /** Constrain end pointer to prevent advancing more than the amount the caller specified |
502 | | * |
503 | | * @param[in] _sbuff to constrain. |
504 | | * @param[in] _max maximum amount to advance. |
505 | | * @param[in] _used how much we've advanced so far. |
506 | | * @return a temporary end pointer. |
507 | | */ |
508 | | #define CONSTRAINED_END(_sbuff, _max, _used) \ |
509 | 234k | (((_max) - (_used)) > fr_sbuff_remaining(_sbuff) ? (_sbuff)->end : (_sbuff)->p + ((_max) - (_used))) |
510 | | |
511 | | |
512 | | /** Populate a terminal index |
513 | | * |
514 | | * @param[out] needle_len the longest needle. Will not be set |
515 | | * if the terminal array is empty. |
516 | | * @param[out] idx to populate. |
517 | | * @param[in] term Terminals to populate the index with. |
518 | | */ |
519 | | static inline CC_HINT(always_inline) void fr_sbuff_terminal_idx_init(size_t *needle_len, |
520 | | uint8_t idx[static SBUFF_CHAR_CLASS], |
521 | | fr_sbuff_term_t const *term) |
522 | 77.6k | { |
523 | 77.6k | size_t i, len, max = 0; |
524 | | |
525 | 77.6k | if (!term) return; |
526 | | |
527 | 76.9k | memset(idx, 0, SBUFF_CHAR_CLASS); |
528 | | |
529 | 1.09M | for (i = 0; i < term->len; i++) { |
530 | 1.02M | len = term->elem[i].len; |
531 | 1.02M | if (len > max) max = len; |
532 | | |
533 | 1.02M | idx[(uint8_t)term->elem[i].str[0]] = i + 1; |
534 | 1.02M | } |
535 | | |
536 | 76.9k | if (i > 0) *needle_len = max; |
537 | 76.9k | } |
538 | | |
539 | | /** Efficient terminal string search |
540 | | * |
541 | | * Caller should ensure that a buffer extension of needle_len bytes has been requested |
542 | | * before calling this function. |
543 | | * |
544 | | * @param[in] in Sbuff to search in. |
545 | | * @param[in] p Current position (may be ahead of in->p). |
546 | | * @param[in] idx Fastpath index, populated by |
547 | | * fr_sbuff_terminal_idx_init. |
548 | | * @param[in] term terminals to search in. |
549 | | * @param[in] needle_len Length of the longest needle. |
550 | | * @return |
551 | | * - true if found. |
552 | | * - false if not. |
553 | | */ |
554 | | static inline bool fr_sbuff_terminal_search(fr_sbuff_t *in, char const *p, |
555 | | uint8_t idx[static SBUFF_CHAR_CLASS], |
556 | | fr_sbuff_term_t const *term, UNUSED size_t needle_len) |
557 | 25.2M | { |
558 | 25.2M | uint8_t term_idx; |
559 | | |
560 | 25.2M | ssize_t start = 0; |
561 | 25.2M | ssize_t end; |
562 | 25.2M | ssize_t mid; |
563 | | |
564 | 25.2M | size_t remaining; |
565 | | |
566 | 25.2M | if (!term) return false; /* If there's no terminals, we don't need to search */ |
567 | | |
568 | 265k | end = term->len - 1; |
569 | | |
570 | 265k | term_idx = idx[(uint8_t)*p]; /* Fast path */ |
571 | 265k | if (!term_idx) return false; |
572 | | |
573 | 63.5k | if (p > in->end) return false; /* paranoia */ |
574 | | |
575 | | /* |
576 | | * "p" may be ahead of "in->p", as the caller can scan forward without advancing "in->p`". So we |
577 | | * need to measure bytes available from "p". Othwrwise using fr_sbuff_remaining(in) would |
578 | | * over-state the available bytes by (p - in->p) and read past in->end. |
579 | | */ |
580 | 63.5k | remaining = (size_t)(in->end - p); |
581 | | |
582 | | /* |
583 | | * Special case for EOFlike states |
584 | | */ |
585 | 63.5k | if (remaining == 0) { |
586 | 9 | if (!fr_sbuff_is_extendable(in) && (idx['\0'] != 0)) return true; |
587 | 0 | return false; |
588 | 9 | } |
589 | | |
590 | 63.5k | mid = term_idx - 1; /* Inform the mid point from the index */ |
591 | | |
592 | 67.7k | while (start <= end) { |
593 | 66.3k | fr_sbuff_term_elem_t const *elem; |
594 | 66.3k | size_t tlen; |
595 | 66.3k | int ret; |
596 | | |
597 | 66.3k | elem = &term->elem[mid]; |
598 | 66.3k | tlen = elem->len; |
599 | | |
600 | 66.3k | ret = memcmp(p, elem->str, tlen < (size_t)remaining ? tlen : (size_t)remaining); |
601 | 66.3k | if (ret == 0) { |
602 | | /* |
603 | | * If we have more text than the table element, that's fine |
604 | | */ |
605 | 62.1k | if (remaining >= tlen) return true; |
606 | | |
607 | | /* |
608 | | * If input was shorter than the table element we need to |
609 | | * keep searching. |
610 | | */ |
611 | 0 | ret = -1; |
612 | 0 | } |
613 | | |
614 | 4.19k | if (ret < 0) { |
615 | 1.38k | end = mid - 1; |
616 | 2.81k | } else { |
617 | 2.81k | start = mid + 1; |
618 | 2.81k | } |
619 | | |
620 | 4.19k | mid = start + ((end - start) / 2); /* Avoid overflow */ |
621 | 4.19k | } |
622 | | |
623 | 1.42k | return false; |
624 | 63.5k | } |
625 | | |
626 | | /** Compare two terminal elements for ordering purposes |
627 | | * |
628 | | * @param[in] a first terminal to compare. |
629 | | * @param[in] b second terminal to compare. |
630 | | * @return CMP(a,b) |
631 | | */ |
632 | | static inline int8_t terminal_cmp(fr_sbuff_term_elem_t const *a, fr_sbuff_term_elem_t const *b) |
633 | 1.02M | { |
634 | 1.02M | return MEMCMP_FIELDS(a, b, str, len); |
635 | 1.02M | } |
636 | | |
637 | | #if 0 |
638 | | static void fr_sbuff_terminal_debug_tmp(fr_sbuff_term_elem_t const *elem[], size_t len) |
639 | | { |
640 | | size_t i; |
641 | | |
642 | | FR_FAULT_LOG("Terminal count %zu", len); |
643 | | |
644 | | for (i = 0; i < len; i++) FR_FAULT_LOG("\t\"%s\" (%zu)", elem[i] ? elem[i]->str : "NULL", elem[i] ? elem[i]->len : 0); |
645 | | } |
646 | | #endif |
647 | | |
648 | | /** Merge two sets of terminal strings |
649 | | * |
650 | | * @param[in] ctx to allocate the new terminal array in. |
651 | | * @param[in] a first set of terminals to merge. |
652 | | * @param[in] b second set of terminals to merge. |
653 | | * @return A new set of de-duplicated and sorted terminals. |
654 | | */ |
655 | | fr_sbuff_term_t *fr_sbuff_terminals_amerge(TALLOC_CTX *ctx, fr_sbuff_term_t const *a, fr_sbuff_term_t const *b) |
656 | 22.7k | { |
657 | 22.7k | size_t i, j, num; |
658 | 22.7k | fr_sbuff_term_t *out; |
659 | 22.7k | fr_sbuff_term_elem_t const *tmp[SBUFF_CHAR_CLASS]; |
660 | | |
661 | | /* |
662 | | * Check all inputs are pre-sorted. It doesn't break this |
663 | | * function, but it's useful in case the terminal arrays |
664 | | * are defined elsewhere without merging. |
665 | | */ |
666 | 22.7k | #if !defined(NDEBUG) && defined(WITH_VERIFY_PTR) |
667 | 320k | if (a->len) for (i = 0; i < a->len - 1; i++) fr_assert(terminal_cmp(&a->elem[i], &a->elem[i + 1]) < 0); |
668 | 127k | if (b->len) for (i = 0; i < b->len - 1; i++) fr_assert(terminal_cmp(&b->elem[i], &b->elem[i + 1]) < 0); |
669 | 22.7k | #endif |
670 | | |
671 | | /* |
672 | | * Since the inputs are sorted, we can just do an O(n+m) |
673 | | * walk through the arrays, comparing entries across the |
674 | | * two arrays. |
675 | | * |
676 | | * If there are duplicates, we prefer "a", for no particular reason. |
677 | | */ |
678 | 22.7k | num = i = j = 0; |
679 | 229k | while ((i < a->len) && (j < b->len)) { |
680 | 207k | int8_t cmp; |
681 | | |
682 | 207k | cmp = terminal_cmp(&a->elem[i], &b->elem[j]); |
683 | 207k | if (cmp == 0) { |
684 | 14.0k | j++; |
685 | 14.0k | tmp[num++] = &a->elem[i++]; |
686 | | |
687 | 193k | } else if (cmp < 0) { |
688 | 114k | tmp[num++] = &a->elem[i++]; |
689 | | |
690 | 114k | } else if (cmp > 0) { |
691 | 78.3k | tmp[num++] = &b->elem[j++]; |
692 | 78.3k | } |
693 | | |
694 | 207k | fr_assert(num < SBUFF_CHAR_CLASS); |
695 | 207k | } |
696 | | |
697 | | /* |
698 | | * Only one of these will be hit, and it's simpler than nested "if" statements. |
699 | | */ |
700 | 214k | while (i < a->len) tmp[num++] = &a->elem[i++]; |
701 | 58.2k | while (j < b->len) tmp[num++] = &b->elem[j++]; |
702 | | |
703 | 22.7k | out = talloc_pooled_object(ctx, fr_sbuff_term_t, num, num * sizeof(fr_sbuff_term_elem_t)); |
704 | 22.7k | if (unlikely(!out)) return NULL; |
705 | | |
706 | 22.7k | out->elem = talloc_array(out, fr_sbuff_term_elem_t, num); |
707 | 22.7k | if (unlikely(!out->elem)) { |
708 | 0 | talloc_free(out); |
709 | 0 | return NULL; |
710 | 0 | } |
711 | 22.7k | out->len = num; |
712 | | |
713 | 457k | for (i = 0; i < num; i++) out->elem[i] = *tmp[i]; /* copy merged results back */ |
714 | | |
715 | 22.7k | #if !defined(NDEBUG) && defined(WITH_VERIFY_PTR) |
716 | 434k | for (i = 0; i < num - 1; i++) fr_assert(terminal_cmp(&out->elem[i], &out->elem[i + 1]) < 0); |
717 | 22.7k | #endif |
718 | | |
719 | 22.7k | return out; |
720 | 22.7k | } |
721 | | |
722 | | /** Copy as many bytes as possible from a sbuff to a sbuff |
723 | | * |
724 | | * Copy size is limited by available data in sbuff and space in output sbuff. |
725 | | * |
726 | | * @param[out] out Where to copy to. |
727 | | * @param[in] in Where to copy from. Will copy len bytes from current position in buffer. |
728 | | * @param[in] len How many bytes to copy. If SIZE_MAX the entire buffer will be copied. |
729 | | * @return |
730 | | * - 0 no bytes copied. |
731 | | * - >0 the number of bytes copied. |
732 | | */ |
733 | | size_t fr_sbuff_out_bstrncpy(fr_sbuff_t *out, fr_sbuff_t *in, size_t len) |
734 | 150k | { |
735 | 150k | fr_sbuff_t our_in = FR_SBUFF_BIND_CURRENT(in); |
736 | 150k | size_t remaining; |
737 | | |
738 | 150k | CHECK_SBUFF_INIT(in); |
739 | | |
740 | 300k | while (fr_sbuff_used_total(&our_in) < len) { |
741 | 234k | size_t chunk_len; |
742 | | |
743 | 234k | remaining = (len - fr_sbuff_used_total(&our_in)); |
744 | | |
745 | 234k | if (!fr_sbuff_extend(&our_in)) break; |
746 | | |
747 | 150k | chunk_len = fr_sbuff_remaining(&our_in); |
748 | 150k | if (chunk_len > remaining) chunk_len = remaining; |
749 | | |
750 | 150k | FILL_OR_GOTO_DONE(out, &our_in, chunk_len); |
751 | 150k | } |
752 | | |
753 | 150k | done: |
754 | 150k | *out->p = '\0'; |
755 | 150k | return fr_sbuff_used_total(&our_in); |
756 | 150k | } |
757 | | |
758 | | /** Copy exactly len bytes from a sbuff to a sbuff or fail |
759 | | * |
760 | | * Copy size is limited by available data in sbuff, space in output sbuff, and length. |
761 | | * |
762 | | * @param[out] out Where to copy to. |
763 | | * @param[in] in Where to copy from. Will copy len bytes from current position in buffer. |
764 | | * @param[in] len How many bytes to copy. If SIZE_MAX the entire buffer will be copied. |
765 | | * @return |
766 | | * - 0 no bytes copied, no token found of sufficient length in input buffer. |
767 | | * - >0 the number of bytes copied. |
768 | | * - <0 the number of additional output bytes we would have needed to |
769 | | * complete the copy. |
770 | | */ |
771 | | ssize_t fr_sbuff_out_bstrncpy_exact(fr_sbuff_t *out, fr_sbuff_t *in, size_t len) |
772 | 3 | { |
773 | 3 | fr_sbuff_t our_in = FR_SBUFF(in); |
774 | 3 | size_t remaining; |
775 | 3 | fr_sbuff_marker_t m; |
776 | | |
777 | 3 | CHECK_SBUFF_INIT(in); |
778 | | |
779 | 3 | fr_sbuff_marker(&m, out); |
780 | | |
781 | 3 | do { |
782 | 3 | size_t chunk_len; |
783 | 3 | ssize_t copied; |
784 | | |
785 | 3 | remaining = (len - fr_sbuff_used_total(&our_in)); |
786 | 3 | if (remaining && !fr_sbuff_extend(&our_in)) { |
787 | 0 | fr_sbuff_marker_release(&m); |
788 | 0 | return 0; |
789 | 0 | } |
790 | | |
791 | 3 | chunk_len = fr_sbuff_remaining(&our_in); |
792 | 3 | if (chunk_len > remaining) chunk_len = remaining; |
793 | | |
794 | 3 | copied = fr_sbuff_in_bstrncpy(out, our_in.p, chunk_len); |
795 | 3 | if (copied < 0) { |
796 | 0 | fr_sbuff_set(out, &m); /* Reset out */ |
797 | 0 | *m.p = '\0'; /* Re-terminate */ |
798 | | |
799 | | /* Amount remaining in input buffer minus the amount we could have copied */ |
800 | 0 | if (len == SIZE_MAX) { |
801 | 0 | fr_sbuff_marker_release(&m); |
802 | 0 | return -(fr_sbuff_remaining(in) - (chunk_len + copied)); |
803 | 0 | } |
804 | | /* Amount remaining to copy minus the amount we could have copied */ |
805 | 0 | fr_sbuff_marker_release(&m); |
806 | 0 | return -(remaining - (chunk_len + copied)); |
807 | 0 | } |
808 | 3 | fr_sbuff_advance(&our_in, copied); |
809 | 3 | } while (fr_sbuff_used_total(&our_in) < len); |
810 | | |
811 | 3 | fr_sbuff_marker_release(&m); |
812 | | |
813 | 3 | FR_SBUFF_SET_RETURN(in, &our_in); /* in was pinned, so this works */ |
814 | 3 | } |
815 | | |
816 | | /** Copy as many allowed characters as possible from a sbuff to a sbuff |
817 | | * |
818 | | * Copy size is limited by available data in sbuff and output buffer length. |
819 | | * |
820 | | * As soon as a disallowed character is found the copy is stopped. |
821 | | * The input sbuff will be left pointing at the first disallowed character. |
822 | | * |
823 | | * @param[out] out Where to copy to. |
824 | | * @param[in] in Where to copy from. Will copy len bytes from current position in buffer. |
825 | | * @param[in] len How many bytes to copy. If SIZE_MAX the entire buffer will be copied. |
826 | | * @param[in] allowed Characters to include the copy. |
827 | | * @return |
828 | | * - 0 no bytes copied. |
829 | | * - >0 the number of bytes copied. |
830 | | */ |
831 | | size_t fr_sbuff_out_bstrncpy_allowed(fr_sbuff_t *out, fr_sbuff_t *in, size_t len, |
832 | | bool const allowed[static SBUFF_CHAR_CLASS]) |
833 | 23.4k | { |
834 | 23.4k | fr_sbuff_t our_in = FR_SBUFF_BIND_CURRENT(in); |
835 | | |
836 | 23.4k | CHECK_SBUFF_INIT(in); |
837 | | |
838 | 37.5k | while (fr_sbuff_used_total(&our_in) < len) { |
839 | 37.5k | char *p; |
840 | 37.5k | char *end; |
841 | | |
842 | 37.5k | if (!fr_sbuff_extend(&our_in)) break; |
843 | | |
844 | 23.4k | p = fr_sbuff_current(&our_in); |
845 | 23.4k | end = CONSTRAINED_END(&our_in, len, fr_sbuff_used_total(&our_in)); |
846 | | |
847 | 7.79M | while ((p < end) && allowed[(uint8_t)*p]) p++; |
848 | | |
849 | 23.4k | FILL_OR_GOTO_DONE(out, &our_in, p - our_in.p); |
850 | | |
851 | 23.3k | if (p != end) break; /* stopped early, break */ |
852 | 23.3k | } |
853 | | |
854 | 23.4k | done: |
855 | 23.4k | *out->p = '\0'; |
856 | 23.4k | return fr_sbuff_used_total(&our_in); |
857 | 23.4k | } |
858 | | |
859 | | /** Copy as many allowed characters as possible from a sbuff to a sbuff |
860 | | * |
861 | | * Copy size is limited by available data in sbuff and output buffer length. |
862 | | * |
863 | | * As soon as a disallowed character is found the copy is stopped. |
864 | | * The input sbuff will be left pointing at the first disallowed character. |
865 | | * |
866 | | * @param[out] out Where to copy to. |
867 | | * @param[in] in Where to copy from. Will copy len bytes from current position in buffer. |
868 | | * @param[in] len How many bytes to copy. If SIZE_MAX the entire buffer will be copied. |
869 | | * @param[in] tt Token terminals in the encompassing grammar. |
870 | | * @param[in] u_rules If not NULL, ignore characters in the until set when |
871 | | * prefixed with u_rules->chr. FIXME - Should actually evaluate |
872 | | * u_rules fully. |
873 | | * @return |
874 | | * - 0 no bytes copied. |
875 | | * - >0 the number of bytes copied. |
876 | | */ |
877 | | size_t fr_sbuff_out_bstrncpy_until(fr_sbuff_t *out, fr_sbuff_t *in, size_t len, |
878 | | fr_sbuff_term_t const *tt, |
879 | | fr_sbuff_unescape_rules_t const *u_rules) |
880 | 20.0k | { |
881 | 20.0k | fr_sbuff_t our_in = FR_SBUFF_BIND_CURRENT(in); |
882 | 20.0k | bool do_escape = false; /* Track state across extensions */ |
883 | | |
884 | 20.0k | uint8_t idx[SBUFF_CHAR_CLASS]; /* Fast path index */ |
885 | 20.0k | size_t needle_len = 1; |
886 | 20.0k | char escape_chr = u_rules ? u_rules->chr : '\0'; |
887 | | |
888 | 20.0k | CHECK_SBUFF_INIT(in); |
889 | | |
890 | | /* |
891 | | * Initialise the fastpath index and |
892 | | * figure out the longest needle. |
893 | | */ |
894 | 20.0k | fr_sbuff_terminal_idx_init(&needle_len, idx, tt); |
895 | | |
896 | 20.4k | while (fr_sbuff_used_total(&our_in) < len) { |
897 | 20.4k | char *p; |
898 | 20.4k | char *end; |
899 | | |
900 | 20.4k | if (fr_sbuff_extend_lowat(NULL, &our_in, needle_len) == 0) break; |
901 | | |
902 | 20.0k | p = fr_sbuff_current(&our_in); |
903 | 20.0k | end = CONSTRAINED_END(&our_in, len, fr_sbuff_used_total(&our_in)); |
904 | | |
905 | 20.0k | if (p == end) break; |
906 | | |
907 | 20.0k | if (escape_chr == '\0') { |
908 | 25.0M | while ((p < end) && !fr_sbuff_terminal_search(in, p, idx, tt, needle_len)) p++; |
909 | 20.0k | } else { |
910 | 0 | while (p < end) { |
911 | 0 | if (do_escape) { |
912 | 0 | do_escape = false; |
913 | 0 | } else if (*p == escape_chr) { |
914 | 0 | do_escape = true; |
915 | 0 | } else if (fr_sbuff_terminal_search(in, p, idx, tt, needle_len)) { |
916 | 0 | break; |
917 | 0 | } |
918 | 0 | p++; |
919 | 0 | } |
920 | 0 | } |
921 | | |
922 | 20.0k | FILL_OR_GOTO_DONE(out, &our_in, p - our_in.p); |
923 | | |
924 | 20.0k | if (p != end) break; /* stopped early, break */ |
925 | 20.0k | } |
926 | | |
927 | 20.0k | done: |
928 | 20.0k | *out->p = '\0'; |
929 | 20.0k | return fr_sbuff_used_total(&our_in); |
930 | 20.0k | } |
931 | | |
932 | | /** Copy as many allowed characters as possible from a sbuff to a sbuff |
933 | | * |
934 | | * Copy size is limited by available data in sbuff and output buffer length. |
935 | | * |
936 | | * As soon as a disallowed character is found the copy is stopped. |
937 | | * The input sbuff will be left pointing at the first disallowed character. |
938 | | * |
939 | | * This de-escapes characters as they're copied out of the sbuff. |
940 | | * |
941 | | * @param[out] out Where to copy to. |
942 | | * @param[in] in Where to copy from. Will copy len bytes from current position in buffer. |
943 | | * @param[in] len How many bytes to copy. If SIZE_MAX the entire buffer will be copied. |
944 | | * @param[in] tt Token terminal strings in the encompassing grammar. |
945 | | * @param[in] u_rules for processing unescape sequences. |
946 | | * @return |
947 | | * - 0 no bytes copied. |
948 | | * - >0 the number of bytes written to out. |
949 | | */ |
950 | | size_t fr_sbuff_out_unescape_until(fr_sbuff_t *out, fr_sbuff_t *in, size_t len, |
951 | | fr_sbuff_term_t const *tt, |
952 | | fr_sbuff_unescape_rules_t const *u_rules) |
953 | 36.1k | { |
954 | 36.1k | fr_sbuff_t our_in; |
955 | 36.1k | bool do_escape = false; /* Track state across extensions */ |
956 | 36.1k | fr_sbuff_marker_t o_s; |
957 | 36.1k | fr_sbuff_marker_t c_s; |
958 | 36.1k | fr_sbuff_marker_t end; |
959 | | |
960 | 36.1k | uint8_t idx[SBUFF_CHAR_CLASS]; /* Fast path index */ |
961 | 36.1k | size_t needle_len = 1; |
962 | 36.1k | fr_sbuff_extend_status_t status = 0; |
963 | | |
964 | | /* |
965 | | * If we don't need to do unescaping |
966 | | * call a more suitable function. |
967 | | */ |
968 | 36.1k | if (!u_rules || (u_rules->chr == '\0')) return fr_sbuff_out_bstrncpy_until(out, in, len, tt, u_rules); |
969 | | |
970 | 16.0k | CHECK_SBUFF_INIT(in); |
971 | | |
972 | 16.0k | our_in = FR_SBUFF(in); |
973 | | |
974 | | /* |
975 | | * Chunk tracking... |
976 | | */ |
977 | 16.0k | fr_sbuff_marker(&c_s, &our_in); |
978 | 16.0k | fr_sbuff_marker(&end, &our_in); |
979 | 16.0k | fr_sbuff_marker_update_end(&end, len); |
980 | | |
981 | 16.0k | fr_sbuff_marker(&o_s, out); |
982 | | |
983 | | /* |
984 | | * Initialise the fastpath index and |
985 | | * figure out the longest needle. |
986 | | */ |
987 | 16.0k | fr_sbuff_terminal_idx_init(&needle_len, idx, tt); |
988 | | |
989 | | /* |
990 | | * ...while we have remaining data |
991 | | */ |
992 | 124k | while (fr_sbuff_extend_lowat(&status, &our_in, needle_len) > 0) { |
993 | 124k | if (fr_sbuff_was_extended(status)) fr_sbuff_marker_update_end(&end, len); |
994 | 124k | if (!fr_sbuff_diff(&our_in, &end)) break; /* Reached the end */ |
995 | | |
996 | 124k | if (do_escape) { |
997 | 1.07k | do_escape = false; |
998 | | |
999 | | /* |
1000 | | * Check for \x<hex><hex> |
1001 | | */ |
1002 | 1.07k | if (u_rules->do_hex && fr_sbuff_is_char(&our_in, 'x')) { |
1003 | 319 | uint8_t escape; |
1004 | 319 | fr_sbuff_marker_t m; |
1005 | | |
1006 | 319 | fr_sbuff_marker(&m, &our_in); /* allow for backtrack */ |
1007 | 319 | fr_sbuff_advance(&our_in, 1); /* skip over the 'x' */ |
1008 | | |
1009 | 319 | if (fr_sbuff_out_uint8_hex(NULL, &escape, &our_in, false) != 2) { |
1010 | 55 | fr_sbuff_set(&our_in, &m); /* backtrack */ |
1011 | 55 | fr_sbuff_marker_release(&m); |
1012 | 55 | goto check_subs; /* allow sub for \x */ |
1013 | 55 | } |
1014 | | |
1015 | 264 | if (fr_sbuff_in_char(out, escape) <= 0) { |
1016 | 0 | fr_sbuff_set(&our_in, &m); /* backtrack */ |
1017 | 0 | fr_sbuff_marker_release(&m); |
1018 | 0 | break; |
1019 | 0 | } |
1020 | 264 | fr_sbuff_marker_release(&m); |
1021 | 264 | fr_sbuff_set(&c_s, &our_in); |
1022 | 264 | continue; |
1023 | 264 | } |
1024 | | |
1025 | | /* |
1026 | | * Check for \<oct><oct><oct> |
1027 | | */ |
1028 | 757 | if (u_rules->do_oct && fr_sbuff_is_digit(&our_in)) { |
1029 | 578 | uint8_t escape; |
1030 | 578 | fr_sbuff_marker_t m; |
1031 | | |
1032 | 578 | fr_sbuff_marker(&m, &our_in); /* allow for backtrack */ |
1033 | | |
1034 | 578 | if (fr_sbuff_out_uint8_oct(NULL, &escape, &our_in, false) != 3) { |
1035 | 535 | fr_sbuff_set(&our_in, &m); /* backtrack */ |
1036 | 535 | fr_sbuff_marker_release(&m); |
1037 | 535 | goto check_subs; /* allow sub for \<oct> */ |
1038 | 535 | } |
1039 | | |
1040 | 43 | if (fr_sbuff_in_char(out, escape) <= 0) { |
1041 | 0 | fr_sbuff_set(&our_in, &m); /* backtrack */ |
1042 | 0 | fr_sbuff_marker_release(&m); |
1043 | 0 | break; |
1044 | 0 | } |
1045 | 43 | fr_sbuff_marker_release(&m); |
1046 | 43 | fr_sbuff_set(&c_s, &our_in); |
1047 | 43 | continue; |
1048 | 43 | } |
1049 | | |
1050 | 769 | check_subs: |
1051 | | /* |
1052 | | * Not a recognised hex or octal escape sequence |
1053 | | * may be a substitution or a sequence that |
1054 | | * should be copied to the output buffer. |
1055 | | */ |
1056 | 769 | { |
1057 | 769 | uint8_t c = *fr_sbuff_current(&our_in); |
1058 | | |
1059 | 769 | if (u_rules->subs[c] == '\0') { |
1060 | 769 | if (u_rules->skip[c] == true) goto next; |
1061 | 737 | goto next_esc; |
1062 | 769 | } |
1063 | | |
1064 | | /* |
1065 | | * We already copied everything up |
1066 | | * to this point, so we can now |
1067 | | * write the substituted char to |
1068 | | * the output buffer. |
1069 | | */ |
1070 | 0 | if (fr_sbuff_in_char(out, u_rules->subs[c]) <= 0) break; |
1071 | | |
1072 | | /* |
1073 | | * ...and advance past the entire |
1074 | | * escape seq in the input buffer. |
1075 | | */ |
1076 | 0 | fr_sbuff_advance(&our_in, 1); |
1077 | 0 | fr_sbuff_set(&c_s, &our_in); |
1078 | 0 | continue; |
1079 | 0 | } |
1080 | 0 | } |
1081 | | |
1082 | 124k | next_esc: |
1083 | 124k | if (*fr_sbuff_current(&our_in) == u_rules->chr) { |
1084 | | /* |
1085 | | * Copy out any data we got before |
1086 | | * we hit the escape char. |
1087 | | * |
1088 | | * We need to do this before we |
1089 | | * can write the escape char to |
1090 | | * the output sbuff. |
1091 | | */ |
1092 | 1.07k | FILL_OR_GOTO_DONE(out, &c_s, fr_sbuff_behind(&c_s)); |
1093 | | |
1094 | 1.07k | do_escape = true; |
1095 | 1.07k | fr_sbuff_advance(&our_in, 1); |
1096 | 1.07k | continue; |
1097 | 1.07k | } |
1098 | | |
1099 | 123k | next: |
1100 | 123k | if (tt && fr_sbuff_terminal_search(&our_in, fr_sbuff_current(&our_in), idx, tt, needle_len)) break; |
1101 | 107k | fr_sbuff_advance(&our_in, 1); |
1102 | 107k | } |
1103 | | |
1104 | | /* |
1105 | | * Copy any remaining data over |
1106 | | */ |
1107 | 16.0k | FILL_OR_GOTO_DONE(out, &c_s, fr_sbuff_behind(&c_s)); |
1108 | | |
1109 | 16.0k | done: |
1110 | 16.0k | fr_sbuff_set(in, &c_s); /* Only advance by as much as we copied */ |
1111 | 16.0k | *out->p = '\0'; |
1112 | | |
1113 | 16.0k | return fr_sbuff_marker_release_behind(&o_s); |
1114 | 16.0k | } |
1115 | | |
1116 | | /** See if the string contains a truth value |
1117 | | * |
1118 | | * @param[out] out Where to write boolean value. |
1119 | | * @param[in] in Where to search for a truth value. |
1120 | | * @return |
1121 | | * - >0 the number of bytes consumed. |
1122 | | * - -1 no bytes copied, was not a truth value. |
1123 | | */ |
1124 | | fr_slen_t fr_sbuff_out_bool(bool *out, fr_sbuff_t *in) |
1125 | 20.5k | { |
1126 | 20.5k | fr_sbuff_t our_in = FR_SBUFF(in); |
1127 | | |
1128 | 20.5k | static bool const bool_prefix[SBUFF_CHAR_CLASS] = { |
1129 | 20.5k | ['t'] = true, ['T'] = true, /* true */ |
1130 | 20.5k | ['f'] = true, ['F'] = true, /* false */ |
1131 | 20.5k | ['y'] = true, ['Y'] = true, /* yes */ |
1132 | 20.5k | ['n'] = true, ['N'] = true, /* no */ |
1133 | 20.5k | }; |
1134 | | |
1135 | 20.5k | if (fr_sbuff_is_in_charset(&our_in, bool_prefix)) { |
1136 | 669 | switch (tolower(fr_sbuff_uint8(&our_in, '\0'))) { |
1137 | 0 | default: |
1138 | 0 | break; |
1139 | | |
1140 | 12 | case 't': |
1141 | 12 | if (fr_sbuff_adv_past_strcase_literal(&our_in, "true")) { |
1142 | 2 | *out = true; |
1143 | 2 | FR_SBUFF_SET_RETURN(in, &our_in); |
1144 | 2 | } |
1145 | 10 | break; |
1146 | | |
1147 | 50 | case 'f': |
1148 | 50 | if (fr_sbuff_adv_past_strcase_literal(&our_in, "false")) { |
1149 | 30 | *out = false; |
1150 | 30 | FR_SBUFF_SET_RETURN(in, &our_in); |
1151 | 30 | } |
1152 | 20 | break; |
1153 | | |
1154 | 20 | case 'y': |
1155 | 4 | if (fr_sbuff_adv_past_strcase_literal(&our_in, "yes")) { |
1156 | 2 | *out = true; |
1157 | 2 | FR_SBUFF_SET_RETURN(in, &our_in); |
1158 | 2 | } |
1159 | 2 | break; |
1160 | | |
1161 | 603 | case 'n': |
1162 | 603 | if (fr_sbuff_adv_past_strcase_literal(&our_in, "no")) { |
1163 | 3 | *out = false; |
1164 | 3 | FR_SBUFF_SET_RETURN(in, &our_in); |
1165 | 3 | } |
1166 | 600 | break; |
1167 | 669 | } |
1168 | 669 | } |
1169 | | |
1170 | 20.4k | *out = false; /* Always initialise out */ |
1171 | | |
1172 | 20.4k | fr_strerror_const("Not a valid boolean value. Accepted values are 'yes', 'no', 'true', 'false'"); |
1173 | | |
1174 | 20.4k | return -1; |
1175 | 20.5k | } |
1176 | | |
1177 | | /** Used to define a number parsing functions for signed integers |
1178 | | * |
1179 | | * @param[in] _name Function suffix. |
1180 | | * @param[in] _type Output type. |
1181 | | * @param[in] _min value. |
1182 | | * @param[in] _max value. |
1183 | | * @param[in] _max_char Maximum digits that can be used to represent an integer. |
1184 | | * Can't use stringify because of width modifiers like 'u' |
1185 | | * used in <stdint.h>. |
1186 | | * @param[in] _base to use. |
1187 | | */ |
1188 | | #define SBUFF_PARSE_INT_DEF(_name, _type, _min, _max, _max_char, _base) \ |
1189 | 14.4k | fr_slen_t fr_sbuff_out_##_name(fr_sbuff_parse_error_t *err, _type *out, fr_sbuff_t *in, bool no_trailing) \ |
1190 | 14.4k | { \ |
1191 | 14.4k | char buff[_max_char + 1]; \ |
1192 | 14.4k | char *end, *a_end; \ |
1193 | 14.4k | size_t len; \ |
1194 | 14.4k | long long num; \ |
1195 | 14.4k | _type cast_num; \ |
1196 | 14.4k | fr_sbuff_t our_in = FR_SBUFF(in); \ |
1197 | 14.4k | buff[0] = '\0'; /* clang scan */ \ |
1198 | 14.4k | len = fr_sbuff_out_bstrncpy(&FR_SBUFF_IN(buff, sizeof(buff)), &our_in, _max_char); \ |
1199 | 14.4k | if (len == 0) { \ |
1200 | 15 | if (err) *err = (fr_sbuff_remaining(in) == 0) ? FR_SBUFF_PARSE_ERROR_INPUT_EMPTY : FR_SBUFF_PARSE_ERROR_NOT_FOUND; \ |
1201 | 15 | return -1; \ |
1202 | 15 | } \ |
1203 | 14.4k | errno = 0; /* this is needed as strtoll doesn't reset errno */ \ |
1204 | 14.4k | num = strtoll(buff, &end, _base); \ |
1205 | 14.4k | cast_num = (_type)(num); \ |
1206 | 14.4k | if (end == buff) { \ |
1207 | 939 | if (err) *err = FR_SBUFF_PARSE_ERROR_NOT_FOUND; \ |
1208 | 939 | return -1; \ |
1209 | 939 | } \ |
1210 | 14.4k | if (num > cast_num) { \ |
1211 | 77 | overflow: \ |
1212 | 77 | if (err) *err = FR_SBUFF_PARSE_ERROR_NUM_OVERFLOW; \ |
1213 | 77 | *out = (_type)(_max); \ |
1214 | 77 | return -1; \ |
1215 | 51 | } \ |
1216 | 13.5k | if (((errno == EINVAL) && (num == 0)) || ((errno == ERANGE) && (num == LLONG_MAX))) goto overflow; \ |
1217 | 13.4k | if (num < cast_num) { \ |
1218 | 437 | underflow: \ |
1219 | 437 | if (err) *err = FR_SBUFF_PARSE_ERROR_NUM_UNDERFLOW; \ |
1220 | 437 | *out = (_type)(_min); \ |
1221 | 437 | return -1; \ |
1222 | 18 | } \ |
1223 | 13.4k | if ((errno == ERANGE) && (num == LLONG_MIN)) goto underflow; \ |
1224 | 13.4k | if (no_trailing && ((a_end = in->p + (end - buff)) < in->end)) { \ |
1225 | 12.4k | if (isdigit((uint8_t) *a_end) || (((_base > 10) || ((_base == 0) && (len > 2) && (buff[0] == '0') && (buff[1] == 'x'))) && \ |
1226 | 12.1k | ((tolower((uint8_t) *a_end) >= 'a') && (tolower((uint8_t) *a_end) <= 'f')))) { \ |
1227 | 316 | if (err) *err = FR_SBUFF_PARSE_ERROR_TRAILING; \ |
1228 | 316 | *out = (_type)(_max); \ |
1229 | 316 | FR_SBUFF_ERROR_RETURN(&our_in); \ |
1230 | 316 | } \ |
1231 | 12.4k | *out = cast_num; \ |
1232 | 12.1k | } else { \ |
1233 | 548 | if (err) *err = FR_SBUFF_PARSE_OK; \ |
1234 | 548 | *out = cast_num; \ |
1235 | 548 | } \ |
1236 | 13.0k | return fr_sbuff_advance(in, end - buff); /* Advance by the length strtoll gives us */ \ |
1237 | 13.0k | } |
1238 | | |
1239 | 199 | SBUFF_PARSE_INT_DEF(int8, int8_t, INT8_MIN, INT8_MAX, 4, 0) |
1240 | 630 | SBUFF_PARSE_INT_DEF(int16, int16_t, INT16_MIN, INT16_MAX, 6, 0) |
1241 | 185 | SBUFF_PARSE_INT_DEF(int32, int32_t, INT32_MIN, INT32_MAX, 11, 0) |
1242 | 13.4k | SBUFF_PARSE_INT_DEF(int64, int64_t, INT64_MIN, INT64_MAX, 20, 0) |
1243 | 0 | SBUFF_PARSE_INT_DEF(ssize, ssize_t, SSIZE_MIN, SSIZE_MAX, 20, 0) |
1244 | | |
1245 | | /** Used to define a number parsing functions for signed integers |
1246 | | * |
1247 | | * @param[in] _name Function suffix. |
1248 | | * @param[in] _type Output type. |
1249 | | * @param[in] _max value. |
1250 | | * @param[in] _max_char Maximum digits that can be used to represent an integer. |
1251 | | * Can't use stringify because of width modifiers like 'u' |
1252 | | * used in <stdint.h>. |
1253 | | * @param[in] _base of the number being parsed, 8, 10, 16 etc... |
1254 | | */ |
1255 | | #define SBUFF_PARSE_UINT_DEF(_name, _type, _max, _max_char, _base) \ |
1256 | 135k | fr_slen_t fr_sbuff_out_##_name(fr_sbuff_parse_error_t *err, _type *out, fr_sbuff_t *in, bool no_trailing) \ |
1257 | 135k | { \ |
1258 | 135k | char buff[_max_char + 1]; \ |
1259 | 135k | char *end, *a_end; \ |
1260 | 135k | size_t len; \ |
1261 | 135k | unsigned long long num; \ |
1262 | 135k | _type cast_num; \ |
1263 | 135k | fr_sbuff_t our_in = FR_SBUFF(in); \ |
1264 | 135k | buff[0] = '\0'; /* clang scan */ \ |
1265 | 135k | len = fr_sbuff_out_bstrncpy(&FR_SBUFF_IN(buff, sizeof(buff)), &our_in, _max_char); \ |
1266 | 135k | if (len == 0) { \ |
1267 | 126 | if (err) *err = (fr_sbuff_remaining(in) == 0) ? FR_SBUFF_PARSE_ERROR_INPUT_EMPTY : FR_SBUFF_PARSE_ERROR_NOT_FOUND; \ |
1268 | 126 | return -1; \ |
1269 | 126 | } \ |
1270 | 135k | if (buff[0] == '-') { \ |
1271 | 4.92k | if (err) *err = FR_SBUFF_PARSE_ERROR_NUM_UNDERFLOW; \ |
1272 | 4.92k | return -1; \ |
1273 | 4.92k | } \ |
1274 | 135k | errno = 0; /* this is needed as strtoull doesn't reset errno */ \ |
1275 | 130k | num = strtoull(buff, &end, _base); \ |
1276 | 130k | cast_num = (_type)(num); \ |
1277 | 130k | if (end == buff) { \ |
1278 | 19.1k | if (err) *err = FR_SBUFF_PARSE_ERROR_NOT_FOUND; \ |
1279 | 19.1k | return -1; \ |
1280 | 19.1k | } \ |
1281 | 130k | if (num > cast_num) { \ |
1282 | 5.05k | overflow: \ |
1283 | 5.05k | if (err) *err = FR_SBUFF_PARSE_ERROR_NUM_OVERFLOW; \ |
1284 | 5.05k | *out = (_type)(_max); \ |
1285 | 5.05k | return -1; \ |
1286 | 4.45k | } \ |
1287 | 111k | if (((errno == EINVAL) && (num == 0)) || ((errno == ERANGE) && (num == ULLONG_MAX))) goto overflow; \ |
1288 | 107k | if (no_trailing && ((a_end = in->p + (end - buff)) < in->end)) { \ |
1289 | 34.3k | if (isdigit((uint8_t) *a_end) || (((_base > 10) || ((_base == 0) && (len > 2) && (buff[0] == '0') && (buff[1] == 'x'))) && \ |
1290 | 33.0k | ((tolower((uint8_t) *a_end) >= 'a') && (tolower((uint8_t) *a_end) <= 'f')))) { \ |
1291 | 1.47k | if (err) *err = FR_SBUFF_PARSE_ERROR_TRAILING; \ |
1292 | 1.47k | *out = (_type)(_max); \ |
1293 | 1.47k | FR_SBUFF_ERROR_RETURN(&our_in); \ |
1294 | 1.47k | } \ |
1295 | 34.3k | if (err) *err = FR_SBUFF_PARSE_OK; \ |
1296 | 32.8k | *out = cast_num; \ |
1297 | 72.4k | } else { \ |
1298 | 72.4k | if (err) *err = FR_SBUFF_PARSE_OK; \ |
1299 | 72.4k | *out = cast_num; \ |
1300 | 72.4k | } \ |
1301 | 106k | return fr_sbuff_advance(in, end - buff); /* Advance by the length strtoull gives us */ \ |
1302 | 106k | } |
1303 | | |
1304 | | /* max chars here is the octal string value with prefix */ |
1305 | 45.6k | SBUFF_PARSE_UINT_DEF(uint8, uint8_t, UINT8_MAX, 4, 0) |
1306 | 3.48k | SBUFF_PARSE_UINT_DEF(uint16, uint16_t, UINT16_MAX, 7, 0) |
1307 | 65.8k | SBUFF_PARSE_UINT_DEF(uint32, uint32_t, UINT32_MAX, 12, 0) |
1308 | 20.0k | SBUFF_PARSE_UINT_DEF(uint64, uint64_t, UINT64_MAX, 23, 0) |
1309 | 0 | SBUFF_PARSE_UINT_DEF(size, size_t, SIZE_MAX, 23, 0) |
1310 | | |
1311 | 0 | SBUFF_PARSE_UINT_DEF(uint8_dec, uint8_t, UINT8_MAX, 3, 0) |
1312 | 0 | SBUFF_PARSE_UINT_DEF(uint16_dec, uint16_t, UINT16_MAX, 4, 0) |
1313 | 0 | SBUFF_PARSE_UINT_DEF(uint32_dec, uint32_t, UINT32_MAX, 10, 0) |
1314 | 0 | SBUFF_PARSE_UINT_DEF(uint64_dec, uint64_t, UINT64_MAX, 19, 0) |
1315 | 0 | SBUFF_PARSE_UINT_DEF(size_dec, size_t, SIZE_MAX, 19, 0) |
1316 | | |
1317 | | |
1318 | 578 | SBUFF_PARSE_UINT_DEF(uint8_oct, uint8_t, UINT8_MAX, 3, 8) |
1319 | 0 | SBUFF_PARSE_UINT_DEF(uint16_oct, uint16_t, UINT16_MAX, 6, 8) |
1320 | 0 | SBUFF_PARSE_UINT_DEF(uint32_oct, uint32_t, UINT32_MAX, 11, 8) |
1321 | 0 | SBUFF_PARSE_UINT_DEF(uint64_oct, uint64_t, UINT64_MAX, 22, 8) |
1322 | 0 | SBUFF_PARSE_UINT_DEF(size_oct, size_t, SIZE_MAX, 22, 8) |
1323 | | |
1324 | 319 | SBUFF_PARSE_UINT_DEF(uint8_hex, uint8_t, UINT8_MAX, 2, 16) |
1325 | 0 | SBUFF_PARSE_UINT_DEF(uint16_hex, uint16_t, UINT16_MAX, 4, 16) |
1326 | 0 | SBUFF_PARSE_UINT_DEF(uint32_hex, uint32_t, UINT32_MAX, 8, 16) |
1327 | 0 | SBUFF_PARSE_UINT_DEF(uint64_hex, uint64_t, UINT64_MAX, 16, 16) |
1328 | 0 | SBUFF_PARSE_UINT_DEF(size_hex, size_t, SIZE_MAX, 22, 16) |
1329 | | |
1330 | | /** Used to define a number parsing functions for floats |
1331 | | * |
1332 | | * @param[in] _name Function suffix. |
1333 | | * @param[in] _type Output type. |
1334 | | * @param[in] _func Parsing function to use. |
1335 | | * @param[in] _max_char Maximum digits that can be used to represent an integer. |
1336 | | * Can't use stringify because of width modifiers like 'u' |
1337 | | * used in <stdint.h>. |
1338 | | */ |
1339 | | #define SBUFF_PARSE_FLOAT_DEF(_name, _type, _func, _max_char) \ |
1340 | 4.63k | fr_slen_t fr_sbuff_out_##_name(fr_sbuff_parse_error_t *err, _type *out, fr_sbuff_t *in, bool no_trailing) \ |
1341 | 4.63k | { \ |
1342 | 4.63k | char buff[_max_char + 1] = ""; \ |
1343 | 4.63k | char *end; \ |
1344 | 4.63k | fr_sbuff_t our_in = FR_SBUFF(in); \ |
1345 | 4.63k | size_t len; \ |
1346 | 4.63k | _type res; \ |
1347 | 4.63k | len = fr_sbuff_out_bstrncpy_allowed(&FR_SBUFF_OUT(buff, sizeof(buff)), &our_in, SIZE_MAX, sbuff_char_class_float); \ |
1348 | 4.63k | if (len == sizeof(buff)) { \ |
1349 | 0 | if (err) *err = FR_SBUFF_PARSE_ERROR_NOT_FOUND; \ |
1350 | 0 | return -1; \ |
1351 | 4.63k | } else if (len == 0) { \ |
1352 | 851 | if (err) *err = (fr_sbuff_remaining(in) == 0) ? FR_SBUFF_PARSE_ERROR_INPUT_EMPTY : FR_SBUFF_PARSE_ERROR_NOT_FOUND; \ |
1353 | 851 | return -1; \ |
1354 | 851 | } \ |
1355 | 4.63k | errno = 0; /* this is needed as parsing functions don't reset errno */ \ |
1356 | 3.78k | res = _func(buff, &end); \ |
1357 | 3.78k | if (errno == ERANGE) { \ |
1358 | 146 | if (res > 0) { \ |
1359 | 122 | if (err) *err = FR_SBUFF_PARSE_ERROR_NUM_OVERFLOW; \ |
1360 | 122 | } else { \ |
1361 | 24 | if (err) *err = FR_SBUFF_PARSE_ERROR_NUM_UNDERFLOW; \ |
1362 | 24 | } \ |
1363 | 146 | return -1; \ |
1364 | 146 | } \ |
1365 | 3.78k | if (no_trailing && (*end != '\0')) { \ |
1366 | 254 | if (err) *err = FR_SBUFF_PARSE_ERROR_TRAILING; \ |
1367 | 254 | FR_SBUFF_ERROR_RETURN(&our_in); \ |
1368 | 254 | } \ |
1369 | 3.63k | *out = res; \ |
1370 | 3.38k | return fr_sbuff_advance(in, end - buff); \ |
1371 | 3.63k | } |
1372 | | |
1373 | 138 | SBUFF_PARSE_FLOAT_DEF(float32, float, strtof, 100) |
1374 | 4.49k | SBUFF_PARSE_FLOAT_DEF(float64, double, strtod, 100) |
1375 | | |
1376 | | /** Move data from one sbuff to another |
1377 | | * |
1378 | | * @note Do not call this function directly use #fr_sbuff_move |
1379 | | * |
1380 | | * Both in and out will be advanced by len, with len set to the shortest |
1381 | | * value between the user specified value, the number of bytes remaining |
1382 | | * in the input buffer (after extension), and the number of bytes remaining |
1383 | | * in the output buffer (after extension). |
1384 | | * |
1385 | | * @param[in] out sbuff to copy data to. |
1386 | | * @param[in] in sbuff to copy data from. |
1387 | | * @param[in] len Maximum length of string to copy. |
1388 | | * @return The amount of data copied. |
1389 | | */ |
1390 | | size_t _fr_sbuff_move_sbuff_to_sbuff(fr_sbuff_t *out, fr_sbuff_t *in, size_t len) |
1391 | 193k | { |
1392 | 193k | size_t o_remaining = fr_sbuff_extend_lowat(NULL, out, len); |
1393 | 193k | size_t i_remaining = fr_sbuff_extend_lowat(NULL, in, len); |
1394 | 193k | size_t to_copy = len; |
1395 | 193k | if (to_copy > o_remaining) to_copy = o_remaining; |
1396 | 193k | if (to_copy > i_remaining) to_copy = i_remaining; |
1397 | 193k | safecpy(fr_sbuff_current(out), fr_sbuff_end(out), fr_sbuff_current(in), fr_sbuff_current(in) + to_copy); |
1398 | 193k | return fr_sbuff_advance(out, fr_sbuff_advance(in, to_copy)); |
1399 | 193k | } |
1400 | | |
1401 | | /** Move data from a marker to an sbuff |
1402 | | * |
1403 | | * @note Do not call this function directly use #fr_sbuff_move |
1404 | | * |
1405 | | * @param[in] out sbuff to copy data to. |
1406 | | * @param[in] in marker to copy data from. |
1407 | | * @param[in] len Maximum length of string to copy. |
1408 | | * @return The amount of data copied. |
1409 | | */ |
1410 | | size_t _fr_sbuff_move_marker_to_sbuff(fr_sbuff_t *out, fr_sbuff_marker_t *in, size_t len) |
1411 | 17.1k | { |
1412 | 17.1k | size_t o_remaining = fr_sbuff_extend_lowat(NULL, out, len); |
1413 | 17.1k | size_t i_remaining = fr_sbuff_extend_lowat(NULL, in, len); |
1414 | 17.1k | size_t to_copy = len; |
1415 | 17.1k | if (to_copy > o_remaining) to_copy = o_remaining; |
1416 | 17.1k | if (to_copy > i_remaining) to_copy = i_remaining; |
1417 | 17.1k | safecpy(fr_sbuff_current(out), fr_sbuff_end(out), fr_sbuff_current(in), fr_sbuff_current(in) + to_copy); |
1418 | 17.1k | return fr_sbuff_advance(out, fr_sbuff_advance(in, to_copy)); |
1419 | 17.1k | } |
1420 | | |
1421 | | /** Move data from one marker to another |
1422 | | * |
1423 | | * @note Do not call this function directly use #fr_sbuff_move |
1424 | | * |
1425 | | * @param[in] out marker to copy data to. |
1426 | | * @param[in] in marker to copy data from. |
1427 | | * @param[in] len Maximum length of string to copy. |
1428 | | * @return The amount of data copied. |
1429 | | */ |
1430 | | size_t _fr_sbuff_move_marker_to_marker(fr_sbuff_marker_t *out, fr_sbuff_marker_t *in, size_t len) |
1431 | 0 | { |
1432 | 0 | size_t o_remaining = fr_sbuff_extend_lowat(NULL, out, len); |
1433 | 0 | size_t i_remaining = fr_sbuff_extend_lowat(NULL, in, len); |
1434 | 0 | size_t to_copy = len; |
1435 | 0 | if (to_copy > o_remaining) to_copy = o_remaining; |
1436 | 0 | if (to_copy > i_remaining) to_copy = i_remaining; |
1437 | 0 | safecpy(fr_sbuff_current(out), fr_sbuff_end(out), fr_sbuff_current(in), fr_sbuff_current(in) + to_copy); |
1438 | 0 | return fr_sbuff_advance(out, fr_sbuff_advance(in, to_copy)); |
1439 | 0 | } |
1440 | | |
1441 | | /** Move data from an sbuff to a marker |
1442 | | * |
1443 | | * @note Do not call this function directly use #fr_sbuff_move |
1444 | | * |
1445 | | * @param[in] out marker to copy data to. |
1446 | | * @param[in] in sbuff to copy data from. |
1447 | | * @param[in] len Maximum length of string to copy. |
1448 | | * @return The amount of data copied. |
1449 | | */ |
1450 | | size_t _fr_sbuff_move_sbuff_to_marker(fr_sbuff_marker_t *out, fr_sbuff_t *in, size_t len) |
1451 | 0 | { |
1452 | 0 | size_t o_remaining = fr_sbuff_extend_lowat(NULL, out, len); |
1453 | 0 | size_t i_remaining = fr_sbuff_extend_lowat(NULL, in, len); |
1454 | 0 | size_t to_copy = len; |
1455 | 0 | if (to_copy > o_remaining) to_copy = o_remaining; |
1456 | 0 | if (to_copy > i_remaining) to_copy = i_remaining; |
1457 | 0 | safecpy(fr_sbuff_current(out), fr_sbuff_end(out), fr_sbuff_current(in), fr_sbuff_current(in) + to_copy); |
1458 | 0 | return fr_sbuff_advance(out, fr_sbuff_advance(in, to_copy)); |
1459 | 0 | } |
1460 | | |
1461 | | /** Copy bytes into the sbuff up to the first \0 |
1462 | | * |
1463 | | * @param[in] sbuff to copy into. |
1464 | | * @param[in] str to copy into buffer. |
1465 | | * @return |
1466 | | * - >= 0 the number of bytes copied into the sbuff. |
1467 | | * - <0 the number of bytes required to complete the copy operation. |
1468 | | */ |
1469 | | ssize_t fr_sbuff_in_strcpy(fr_sbuff_t *sbuff, char const *str) |
1470 | 14.2k | { |
1471 | 14.2k | size_t len; |
1472 | | |
1473 | 14.2k | CHECK_SBUFF_WRITEABLE(sbuff); |
1474 | | |
1475 | 14.2k | len = strlen(str); |
1476 | 14.2k | FR_SBUFF_EXTEND_LOWAT_OR_RETURN(sbuff, len); |
1477 | | |
1478 | 10.9k | safecpy(sbuff->p, sbuff->end, str, str + len); |
1479 | 10.9k | sbuff->p[len] = '\0'; |
1480 | | |
1481 | 10.9k | return fr_sbuff_advance(sbuff, len); |
1482 | 14.2k | } |
1483 | | |
1484 | | /** Copy bytes into the sbuff up to the first \0 |
1485 | | * |
1486 | | * @param[in] sbuff to copy into. |
1487 | | * @param[in] str to copy into buffer. |
1488 | | * @param[in] len number of bytes to copy. |
1489 | | * @return |
1490 | | * - >= 0 the number of bytes copied into the sbuff. |
1491 | | * - <0 the number of bytes required to complete the copy operation. |
1492 | | */ |
1493 | | ssize_t fr_sbuff_in_bstrncpy(fr_sbuff_t *sbuff, char const *str, size_t len) |
1494 | 20.5M | { |
1495 | 20.5M | CHECK_SBUFF_WRITEABLE(sbuff); |
1496 | | |
1497 | 20.5M | FR_SBUFF_EXTEND_LOWAT_OR_RETURN(sbuff, len); |
1498 | | |
1499 | 20.4M | safecpy(sbuff->p, sbuff->end, str, str + len); |
1500 | 20.4M | sbuff->p[len] = '\0'; |
1501 | | |
1502 | 20.4M | return fr_sbuff_advance(sbuff, len); |
1503 | 20.5M | } |
1504 | | |
1505 | | /** Copy bytes into the sbuff up to the first \0 |
1506 | | * |
1507 | | * @param[in] sbuff to copy into. |
1508 | | * @param[in] str talloced buffer to copy into sbuff. |
1509 | | * @return |
1510 | | * - >= 0 the number of bytes copied into the sbuff. |
1511 | | * - <0 the number of bytes required to complete the copy operation. |
1512 | | */ |
1513 | | ssize_t fr_sbuff_in_bstrcpy_buffer(fr_sbuff_t *sbuff, char const *str) |
1514 | 21.2M | { |
1515 | 21.2M | size_t len; |
1516 | | |
1517 | 21.2M | CHECK_SBUFF_WRITEABLE(sbuff); |
1518 | | |
1519 | 21.2M | len = talloc_strlen(str); |
1520 | | |
1521 | 21.2M | FR_SBUFF_EXTEND_LOWAT_OR_RETURN(sbuff, len); |
1522 | | |
1523 | 21.2M | safecpy(sbuff->p, sbuff->end, str, str + len); |
1524 | 21.2M | sbuff->p[len] = '\0'; |
1525 | | |
1526 | 21.2M | return fr_sbuff_advance(sbuff, len); |
1527 | 21.2M | } |
1528 | | |
1529 | | /** Free the scratch buffer used for printf |
1530 | | * |
1531 | | */ |
1532 | | static int _sbuff_scratch_free(void *arg) |
1533 | 30 | { |
1534 | 30 | sbuff_scratch_freed = true; |
1535 | 30 | return talloc_free(arg); |
1536 | 30 | } |
1537 | | |
1538 | | static inline CC_HINT(always_inline) int sbuff_scratch_init(TALLOC_CTX **out) |
1539 | 21.2M | { |
1540 | 21.2M | TALLOC_CTX *scratch; |
1541 | | |
1542 | | /* |
1543 | | * Once main has signalled shutdown the TLS slot may be a |
1544 | | * dangling pointer on threads we don't own; skip the scratch |
1545 | | * cache and let callers allocate at top level instead. The |
1546 | | * TLS-local `sbuff_scratch_freed` is left in place for the |
1547 | | * per-thread teardown path on FR-managed threads. |
1548 | | */ |
1549 | 21.2M | if (sbuff_scratch_freed || fr_atexit_thread_local_alloc_disabled()) { |
1550 | 0 | *out = NULL; |
1551 | 0 | return 0; |
1552 | 0 | } |
1553 | | |
1554 | 21.2M | scratch = sbuff_scratch; |
1555 | 21.2M | if (!scratch) { |
1556 | 30 | scratch = talloc_pool(NULL, 4096); |
1557 | 30 | if (unlikely(!scratch)) { |
1558 | 0 | fr_strerror_const("Out of Memory"); |
1559 | 0 | return -1; |
1560 | 0 | } |
1561 | 30 | fr_atexit_thread_local(sbuff_scratch, _sbuff_scratch_free, scratch); |
1562 | 30 | } |
1563 | | |
1564 | 21.2M | *out = scratch; |
1565 | | |
1566 | 21.2M | return 0; |
1567 | 21.2M | } |
1568 | | |
1569 | | /** Print using a fmt string to an sbuff |
1570 | | * |
1571 | | * @param[in] sbuff to print into. |
1572 | | * @param[in] fmt string. |
1573 | | * @param[in] ap arguments for format string. |
1574 | | < * @return |
1575 | | * - >= 0 the number of bytes printed into the sbuff. |
1576 | | * - <0 the number of bytes required to complete the print operation. |
1577 | | */ |
1578 | | ssize_t fr_sbuff_in_vsprintf(fr_sbuff_t *sbuff, char const *fmt, va_list ap) |
1579 | 21.2M | { |
1580 | 21.2M | TALLOC_CTX *scratch; |
1581 | 21.2M | va_list ap_p; |
1582 | 21.2M | char *tmp; |
1583 | 21.2M | ssize_t slen; |
1584 | | |
1585 | 21.2M | CHECK_SBUFF_WRITEABLE(sbuff); |
1586 | | |
1587 | 21.2M | if (sbuff_scratch_init(&scratch) < 0) return 0; |
1588 | | |
1589 | 21.2M | va_copy(ap_p, ap); |
1590 | 21.2M | tmp = fr_vasprintf(scratch, fmt, ap_p); |
1591 | 21.2M | va_end(ap_p); |
1592 | 21.2M | if (!tmp) return 0; |
1593 | | |
1594 | 21.2M | slen = fr_sbuff_in_bstrcpy_buffer(sbuff, tmp); |
1595 | 21.2M | talloc_free(tmp); /* Free the temporary buffer */ |
1596 | | |
1597 | 21.2M | return slen; |
1598 | 21.2M | } |
1599 | | |
1600 | | /** Print using a fmt string to an sbuff |
1601 | | * |
1602 | | * @param[in] sbuff to print into. |
1603 | | * @param[in] fmt string. |
1604 | | * @param[in] ... arguments for format string. |
1605 | | * @return |
1606 | | * - >= 0 the number of bytes printed into the sbuff. |
1607 | | * - <0 the number of bytes required to complete the print operation. |
1608 | | */ |
1609 | | ssize_t fr_sbuff_in_sprintf(fr_sbuff_t *sbuff, char const *fmt, ...) |
1610 | 21.2M | { |
1611 | 21.2M | va_list ap; |
1612 | 21.2M | ssize_t slen; |
1613 | | |
1614 | 21.2M | CHECK_SBUFF_WRITEABLE(sbuff); |
1615 | | |
1616 | 21.2M | va_start(ap, fmt); |
1617 | 21.2M | slen = fr_sbuff_in_vsprintf(sbuff, fmt, ap); |
1618 | 21.2M | va_end(ap); |
1619 | | |
1620 | 21.2M | return slen; |
1621 | 21.2M | } |
1622 | | |
1623 | | /** Print an escaped string to an sbuff |
1624 | | * |
1625 | | * @param[in] sbuff to print into. |
1626 | | * @param[in] in to escape. |
1627 | | * @param[in] inlen of string to escape. |
1628 | | * @param[in] e_rules Escaping rules. Used to escape special characters |
1629 | | * as data is written to the sbuff. May be NULL. |
1630 | | * @return |
1631 | | * - >= 0 the number of bytes printed into the sbuff. |
1632 | | * - <0 the number of bytes required to complete the print operation. |
1633 | | */ |
1634 | | ssize_t fr_sbuff_in_escape(fr_sbuff_t *sbuff, char const *in, size_t inlen, fr_sbuff_escape_rules_t const *e_rules) |
1635 | 13.0k | { |
1636 | 13.0k | char const *end = in + inlen; |
1637 | 13.0k | char const *p = in; |
1638 | 13.0k | fr_sbuff_t our_sbuff; |
1639 | | |
1640 | | /* Significantly quicker if there are no rules */ |
1641 | 13.0k | if (!e_rules || (e_rules->chr == '\0')) return fr_sbuff_in_bstrncpy(sbuff, in, inlen); |
1642 | | |
1643 | 7.97k | CHECK_SBUFF_WRITEABLE(sbuff); |
1644 | | |
1645 | 7.97k | our_sbuff = FR_SBUFF(sbuff); |
1646 | 39.2M | while (p < end) { |
1647 | 39.2M | size_t clen; |
1648 | 39.2M | uint8_t c = (uint8_t)*p; |
1649 | 39.2M | char sub; |
1650 | | |
1651 | | /* |
1652 | | * We don't support escaping UTF8 sequences |
1653 | | * as they're not used anywhere in our |
1654 | | * grammar. |
1655 | | */ |
1656 | 39.2M | if (e_rules->do_utf8 && ((clen = fr_utf8_char((uint8_t const *)p, end - p)) > 1)) { |
1657 | 164k | FR_SBUFF_IN_BSTRNCPY_RETURN(&our_sbuff, p, clen); |
1658 | 164k | p += clen; |
1659 | 164k | continue; |
1660 | 164k | } |
1661 | | |
1662 | | /* |
1663 | | * Check if there's a special substitution |
1664 | | * like 0x0a -> \n. |
1665 | | */ |
1666 | 39.0M | sub = e_rules->subs[c]; |
1667 | 39.0M | if (sub != '\0') { |
1668 | 432k | FR_SBUFF_IN_CHAR_RETURN(&our_sbuff, e_rules->chr, sub); |
1669 | 432k | p++; |
1670 | 432k | continue; |
1671 | 432k | } |
1672 | | |
1673 | | /* |
1674 | | * Check if the character is in the range |
1675 | | * we escape. |
1676 | | */ |
1677 | 38.6M | if (e_rules->esc[c]) { |
1678 | | /* |
1679 | | * For legacy reasons we prefer |
1680 | | * octal escape sequences. |
1681 | | */ |
1682 | 18.9M | if (e_rules->do_oct) { |
1683 | 18.9M | FR_SBUFF_IN_SPRINTF_RETURN(&our_sbuff, "%c%03o", e_rules->chr, (uint8_t)*p++); |
1684 | 18.9M | continue; |
1685 | 18.9M | } else if (e_rules->do_hex) { |
1686 | 0 | FR_SBUFF_IN_SPRINTF_RETURN(&our_sbuff, "%cx%02x", e_rules->chr, (uint8_t)*p++); |
1687 | 0 | continue; |
1688 | 0 | } |
1689 | 18.9M | } |
1690 | | |
1691 | 19.7M | FR_SBUFF_IN_CHAR_RETURN(&our_sbuff, *p++); |
1692 | 19.7M | } |
1693 | | |
1694 | 7.97k | FR_SBUFF_SET_RETURN(sbuff, &our_sbuff); |
1695 | 7.97k | } |
1696 | | |
1697 | | /** Walk an input string and report whether fr_sbuff_in_escape() would |
1698 | | * escape any characters in it. |
1699 | | * |
1700 | | * Mirrors the per-byte decisions of #fr_sbuff_in_escape: a byte |
1701 | | * inside a multi-byte UTF-8 sequence (when do_utf8 is set) is passed |
1702 | | * through, a byte with a substitution mapping is escaped, and a byte |
1703 | | * in the esc[] table is escaped. If any byte would be escaped, the |
1704 | | * function returns false at that byte. A NULL or chr=='\0' ruleset |
1705 | | * is treated as "no escaping": the function always returns true. |
1706 | | * |
1707 | | * @param[in] in to inspect. |
1708 | | * @param[in] inlen bytes of `in` to inspect. |
1709 | | * @param[in] e_rules escaping rules. May be NULL. |
1710 | | * @return |
1711 | | * - false at least one byte would be escaped. |
1712 | | * - true no byte would be escaped (the string is already safe). |
1713 | | */ |
1714 | | bool fr_sbuff_in_needs_escaping(char const *in, size_t inlen, fr_sbuff_escape_rules_t const *e_rules) |
1715 | 0 | { |
1716 | 0 | char const *end = in + inlen; |
1717 | 0 | char const *p = in; |
1718 | |
|
1719 | 0 | if (!e_rules || !e_rules->chr) return false; |
1720 | | |
1721 | 0 | while (p < end) { |
1722 | 0 | size_t clen; |
1723 | 0 | uint8_t c = (uint8_t) *p; |
1724 | |
|
1725 | 0 | if (e_rules->do_utf8 && ((clen = fr_utf8_char((uint8_t const *) p, end - p)) > 1)) { |
1726 | 0 | p += clen; |
1727 | 0 | continue; |
1728 | 0 | } |
1729 | | |
1730 | 0 | if (e_rules->subs[c] != '\0') return false; |
1731 | | |
1732 | 0 | if (e_rules->esc[c]) return false; |
1733 | | |
1734 | 0 | p++; |
1735 | 0 | } |
1736 | | |
1737 | 0 | return true; |
1738 | 0 | } |
1739 | | |
1740 | | /** Print an escaped string to an sbuff taking a talloced buffer as input |
1741 | | * |
1742 | | * @param[in] sbuff to print into. |
1743 | | * @param[in] in to escape. |
1744 | | * @param[in] e_rules Escaping rules. Used to escape special characters |
1745 | | * as data is written to the sbuff. May be NULL. |
1746 | | * @return |
1747 | | * - >= 0 the number of bytes printed into the sbuff. |
1748 | | * - <0 the number of bytes required to complete the print operation. |
1749 | | */ |
1750 | | ssize_t fr_sbuff_in_escape_buffer(fr_sbuff_t *sbuff, char const *in, fr_sbuff_escape_rules_t const *e_rules) |
1751 | 3.51k | { |
1752 | 3.51k | if (unlikely(!in)) return 0; |
1753 | | |
1754 | 3.51k | CHECK_SBUFF_WRITEABLE(sbuff); |
1755 | | |
1756 | 3.51k | return fr_sbuff_in_escape(sbuff, in, talloc_strlen(in), e_rules); |
1757 | 3.51k | } |
1758 | | |
1759 | | /** Concat an array of strings (NULL terminated), with a string separator |
1760 | | * |
1761 | | * @param[out] out Where to write the resulting string. |
1762 | | * @param[in] array of strings to concat. |
1763 | | * @param[in] sep to insert between elements. May be NULL. |
1764 | | * @return |
1765 | | * - >= 0 on success - length of the string created. |
1766 | | * - <0 on failure. How many bytes we would need. |
1767 | | */ |
1768 | | fr_slen_t fr_sbuff_in_array(fr_sbuff_t *out, char const * const *array, char const *sep) |
1769 | 0 | { |
1770 | 0 | fr_sbuff_t our_out = FR_SBUFF(out); |
1771 | 0 | char const * const * p; |
1772 | 0 | fr_sbuff_escape_rules_t e_rules = { |
1773 | 0 | .name = __FUNCTION__, |
1774 | 0 | .chr = '\\' |
1775 | 0 | }; |
1776 | |
|
1777 | 0 | if (sep) e_rules.subs[(uint8_t)*sep] = *sep; |
1778 | |
|
1779 | 0 | CHECK_SBUFF_WRITEABLE(out); |
1780 | | |
1781 | 0 | for (p = array; *p; p++) { |
1782 | 0 | if (*p) FR_SBUFF_RETURN(fr_sbuff_in_escape, &our_out, *p, strlen(*p), &e_rules); |
1783 | | |
1784 | 0 | if (sep && p[1]) { |
1785 | 0 | FR_SBUFF_RETURN(fr_sbuff_in_strcpy, &our_out, sep); |
1786 | 0 | } |
1787 | 0 | } |
1788 | | |
1789 | 0 | FR_SBUFF_SET_RETURN(out, &our_out); |
1790 | 0 | } |
1791 | | |
1792 | | /** Return true and advance past the end of the needle if needle occurs next in the sbuff |
1793 | | * |
1794 | | * @param[in] sbuff to search in. |
1795 | | * @param[in] needle to search for. |
1796 | | * @param[in] needle_len of needle. If SIZE_MAX strlen is used |
1797 | | * to determine length of the needle. |
1798 | | * @return how many bytes we advanced |
1799 | | */ |
1800 | | size_t fr_sbuff_adv_past_str(fr_sbuff_t *sbuff, char const *needle, size_t needle_len) |
1801 | 75.5k | { |
1802 | 75.5k | char const *found; |
1803 | | |
1804 | 75.5k | CHECK_SBUFF_INIT(sbuff); |
1805 | | |
1806 | 75.5k | if (needle_len == SIZE_MAX) needle_len = strlen(needle); |
1807 | | |
1808 | | /* |
1809 | | * If there's insufficient bytes in the |
1810 | | * buffer currently, try to extend it, |
1811 | | * returning if we can't. |
1812 | | */ |
1813 | 75.5k | if (fr_sbuff_extend_lowat(NULL, sbuff, needle_len) < needle_len) return 0; |
1814 | | |
1815 | 75.3k | found = memmem(sbuff->p, needle_len, needle, needle_len); /* sbuff needle_len and needle needle_len ensures match must be next */ |
1816 | 75.3k | if (!found) return 0; |
1817 | | |
1818 | 3.06k | return fr_sbuff_advance(sbuff, needle_len); |
1819 | 75.3k | } |
1820 | | |
1821 | | /** Return true and advance past the end of the needle if needle occurs next in the sbuff |
1822 | | * |
1823 | | * This function is similar to fr_sbuff_adv_past_str but is case insensitive. |
1824 | | * |
1825 | | * @param[in] sbuff to search in. |
1826 | | * @param[in] needle to search for. |
1827 | | * @param[in] needle_len of needle. If SIZE_MAX strlen is used |
1828 | | * to determine length of the needle. |
1829 | | * @return how many bytes we advanced |
1830 | | */ |
1831 | | size_t fr_sbuff_adv_past_strcase(fr_sbuff_t *sbuff, char const *needle, size_t needle_len) |
1832 | 42.1k | { |
1833 | 42.1k | char const *p, *n_p; |
1834 | 42.1k | char const *end; |
1835 | | |
1836 | 42.1k | CHECK_SBUFF_INIT(sbuff); |
1837 | | |
1838 | 42.1k | if (needle_len == SIZE_MAX) needle_len = strlen(needle); |
1839 | | |
1840 | | /* |
1841 | | * If there's insufficient bytes in the |
1842 | | * buffer currently, try to extend it, |
1843 | | * returning if we can't. |
1844 | | */ |
1845 | 42.1k | if (fr_sbuff_extend_lowat(NULL, sbuff, needle_len) < needle_len) return 0; |
1846 | | |
1847 | 42.0k | p = sbuff->p; |
1848 | 42.0k | end = p + needle_len; |
1849 | | |
1850 | 46.3k | for (p = sbuff->p, n_p = needle; p < end; p++, n_p++) { |
1851 | 45.9k | if (tolower((uint8_t) *p) != tolower((uint8_t) *n_p)) return 0; |
1852 | 45.9k | } |
1853 | | |
1854 | 440 | return fr_sbuff_advance(sbuff, needle_len); |
1855 | 42.0k | } |
1856 | | |
1857 | | /** Wind position past characters in the allowed set |
1858 | | * |
1859 | | * @param[in] sbuff sbuff to search in. |
1860 | | * @param[in] len Maximum amount to advance by. Unconstrained if SIZE_MAX. |
1861 | | * @param[in] allowed character set. |
1862 | | * @param[in] tt If not NULL, stop if we find a terminal sequence. |
1863 | | * @return how many bytes we advanced. |
1864 | | */ |
1865 | | size_t fr_sbuff_adv_past_allowed(fr_sbuff_t *sbuff, size_t len, bool |
1866 | | const allowed[static SBUFF_CHAR_CLASS], fr_sbuff_term_t const *tt) |
1867 | 188k | { |
1868 | 188k | size_t total = 0; |
1869 | 188k | char const *p; |
1870 | 188k | uint8_t idx[SBUFF_CHAR_CLASS]; /* Fast path index */ |
1871 | 188k | size_t needle_len = 0; |
1872 | | |
1873 | 188k | CHECK_SBUFF_INIT(sbuff); |
1874 | | |
1875 | 188k | if (tt) fr_sbuff_terminal_idx_init(&needle_len, idx, tt); |
1876 | | |
1877 | 194k | while (total < len) { |
1878 | 189k | char *end; |
1879 | | |
1880 | 189k | if (!fr_sbuff_extend(sbuff)) break; |
1881 | | |
1882 | 188k | end = CONSTRAINED_END(sbuff, len, total); |
1883 | 188k | p = sbuff->p; |
1884 | 16.9M | while ((p < end) && allowed[(uint8_t)*p]) { |
1885 | 16.7M | if (needle_len == 0) { |
1886 | 16.7M | p++; |
1887 | 16.7M | continue; |
1888 | 16.7M | } |
1889 | | |
1890 | | /* |
1891 | | * If this character is allowed, BUT is also listed as a one-character terminal, |
1892 | | * then we still allow it. This decision implements "greedy" parsing. |
1893 | | */ |
1894 | 0 | if (fr_sbuff_terminal_search(sbuff, p, idx, tt, 1)) { |
1895 | 0 | p++; |
1896 | 0 | continue; |
1897 | 0 | } |
1898 | | |
1899 | | /* |
1900 | | * Otherwise if the next *set* of characters) is not in the terminals, then |
1901 | | * allow the current character. |
1902 | | */ |
1903 | 0 | if (!fr_sbuff_terminal_search(sbuff, p, idx, tt, needle_len)) { |
1904 | 0 | p++; |
1905 | 0 | continue; |
1906 | 0 | } |
1907 | | |
1908 | | /* |
1909 | | * The character is allowed, and is NOT listed as a terminal character by itself. |
1910 | | * However, it is part of a multi-character terminal sequence. We therefore |
1911 | | * stop. |
1912 | | * |
1913 | | * This decision allows us to parse things like "Framed-User", where we might |
1914 | | * normally stop at the "-". However, we will still stop at "Framed-=User", as |
1915 | | * "-=" may be a terminal sequence. |
1916 | | * |
1917 | | * There is no perfect solution here, other than to fix the input grammar so that |
1918 | | * it has no ambiguity. Since we can't do that, we choose to err on the side of |
1919 | | * allowing the existing grammar, where it makes sense |
1920 | | */ |
1921 | 0 | break; |
1922 | 0 | } |
1923 | | |
1924 | 188k | total += fr_sbuff_set(sbuff, p); |
1925 | 188k | if (p != end) break; /* stopped early, break */ |
1926 | 188k | } |
1927 | | |
1928 | 188k | return total; |
1929 | 188k | } |
1930 | | |
1931 | | /** Wind position until we hit a character in the terminal set |
1932 | | * |
1933 | | * @param[in] sbuff sbuff to search in. |
1934 | | * @param[in] len Maximum amount to advance by. Unconstrained if SIZE_MAX. |
1935 | | * @param[in] tt Token terminals in the encompassing grammar. |
1936 | | * @param[in] escape_chr If not '\0', ignore characters in the tt set when |
1937 | | * prefixed with this escape character. |
1938 | | * @return how many bytes we advanced. |
1939 | | */ |
1940 | | size_t fr_sbuff_adv_until(fr_sbuff_t *sbuff, size_t len, fr_sbuff_term_t const *tt, char escape_chr) |
1941 | 1.64k | { |
1942 | 1.64k | size_t total = 0; |
1943 | 1.64k | char const *p; |
1944 | 1.64k | bool do_escape = false; /* Track state across extensions */ |
1945 | | |
1946 | 1.64k | uint8_t idx[SBUFF_CHAR_CLASS]; /* Fast path index */ |
1947 | 1.64k | size_t needle_len = 1; |
1948 | | |
1949 | 1.64k | CHECK_SBUFF_INIT(sbuff); |
1950 | | |
1951 | | /* |
1952 | | * Initialise the fastpath index and |
1953 | | * figure out the longest needle. |
1954 | | */ |
1955 | 1.64k | fr_sbuff_terminal_idx_init(&needle_len, idx, tt); |
1956 | | |
1957 | 2.23k | while (total < len) { |
1958 | 2.10k | char *end; |
1959 | | |
1960 | 2.10k | if (fr_sbuff_extend_lowat(NULL, sbuff, needle_len) == 0) break; |
1961 | | |
1962 | 1.64k | end = CONSTRAINED_END(sbuff, len, total); |
1963 | 1.64k | p = sbuff->p; |
1964 | | |
1965 | 1.64k | if (escape_chr == '\0') { |
1966 | 58.4k | while ((p < end) && !fr_sbuff_terminal_search(sbuff, p, idx, tt, needle_len)) p++; |
1967 | 1.64k | } else { |
1968 | 0 | while (p < end) { |
1969 | 0 | if (do_escape) { |
1970 | 0 | do_escape = false; |
1971 | 0 | } else if (*p == escape_chr) { |
1972 | 0 | do_escape = true; |
1973 | 0 | } else if (fr_sbuff_terminal_search(sbuff, p, idx, tt, needle_len)) { |
1974 | 0 | break; |
1975 | 0 | } |
1976 | 0 | p++; |
1977 | 0 | } |
1978 | 0 | } |
1979 | | |
1980 | 1.64k | total += fr_sbuff_set(sbuff, p); |
1981 | 1.64k | if (p != end) break; /* stopped early, break */ |
1982 | 1.64k | } |
1983 | | |
1984 | 1.64k | return total; |
1985 | 1.64k | } |
1986 | | |
1987 | | /** Wind position to first instance of specified multibyte utf8 char |
1988 | | * |
1989 | | * Only use this function if the search char could be multibyte, |
1990 | | * as there's a large performance penalty. |
1991 | | * |
1992 | | * @param[in,out] sbuff to search in. |
1993 | | * @param[in] len the maximum number of characters to search in sbuff. |
1994 | | * @param[in] chr to search for. |
1995 | | * @return |
1996 | | * - NULL, no instances found. |
1997 | | * - The position of the first character. |
1998 | | */ |
1999 | | char *fr_sbuff_adv_to_chr_utf8(fr_sbuff_t *sbuff, size_t len, char const *chr) |
2000 | 0 | { |
2001 | 0 | fr_sbuff_t our_sbuff = FR_SBUFF(sbuff); |
2002 | 0 | size_t total = 0; |
2003 | 0 | size_t clen = strlen(chr); |
2004 | |
|
2005 | 0 | CHECK_SBUFF_INIT(sbuff); |
2006 | | |
2007 | | /* |
2008 | | * Needle bigger than haystack |
2009 | | */ |
2010 | 0 | if (len < clen) return NULL; |
2011 | | |
2012 | 0 | while (total <= (len - clen)) { |
2013 | 0 | char const *found; |
2014 | 0 | char *end; |
2015 | | |
2016 | | /* |
2017 | | * Ensure we have enough chars to match |
2018 | | * the needle. |
2019 | | */ |
2020 | 0 | if (fr_sbuff_extend_lowat(NULL, &our_sbuff, clen) < clen) break; |
2021 | | |
2022 | 0 | end = CONSTRAINED_END(&our_sbuff, len, total); |
2023 | |
|
2024 | 0 | found = fr_utf8_strchr(NULL, our_sbuff.p, end - our_sbuff.p, chr); |
2025 | 0 | if (found) { |
2026 | 0 | (void)fr_sbuff_set(sbuff, found); |
2027 | 0 | return sbuff->p; |
2028 | 0 | } |
2029 | 0 | total += fr_sbuff_set(&our_sbuff, (end - clen) + 1); |
2030 | 0 | } |
2031 | | |
2032 | 0 | return NULL; |
2033 | 0 | } |
2034 | | |
2035 | | /** Wind position to first instance of specified char |
2036 | | * |
2037 | | * @param[in,out] sbuff to search in. |
2038 | | * @param[in] len Maximum amount to advance by. Unconstrained if SIZE_MAX. |
2039 | | * @param[in] c to search for. |
2040 | | * @return |
2041 | | * - NULL, no instances found. |
2042 | | * - The position of the first character. |
2043 | | */ |
2044 | | char *fr_sbuff_adv_to_chr(fr_sbuff_t *sbuff, size_t len, char c) |
2045 | 0 | { |
2046 | 0 | fr_sbuff_t our_sbuff = FR_SBUFF(sbuff); |
2047 | 0 | size_t total = 0; |
2048 | |
|
2049 | 0 | CHECK_SBUFF_INIT(sbuff); |
2050 | | |
2051 | 0 | while (total < len) { |
2052 | 0 | char const *found; |
2053 | 0 | char *end; |
2054 | |
|
2055 | 0 | if (!fr_sbuff_extend(&our_sbuff)) break; |
2056 | | |
2057 | 0 | end = CONSTRAINED_END(&our_sbuff, len, total); |
2058 | 0 | found = memchr(our_sbuff.p, c, end - our_sbuff.p); |
2059 | 0 | if (found) { |
2060 | 0 | (void)fr_sbuff_set(sbuff, found); |
2061 | 0 | return sbuff->p; |
2062 | 0 | } |
2063 | | |
2064 | 0 | total += fr_sbuff_set(&our_sbuff, end); |
2065 | 0 | } |
2066 | | |
2067 | 0 | return NULL; |
2068 | 0 | } |
2069 | | |
2070 | | /** Wind position to the first instance of the specified needle |
2071 | | * |
2072 | | * @param[in,out] sbuff sbuff to search in. |
2073 | | * @param[in] len Maximum amount to advance by. Unconstrained if SIZE_MAX. |
2074 | | * @param[in] needle to search for. |
2075 | | * @param[in] needle_len Length of the needle. SIZE_MAX to used strlen. |
2076 | | * @return |
2077 | | * - NULL, no instances found. |
2078 | | * - The position of the first character. |
2079 | | */ |
2080 | | char *fr_sbuff_adv_to_str(fr_sbuff_t *sbuff, size_t len, char const *needle, size_t needle_len) |
2081 | 0 | { |
2082 | 0 | fr_sbuff_t our_sbuff = FR_SBUFF(sbuff); |
2083 | 0 | size_t total = 0; |
2084 | |
|
2085 | 0 | CHECK_SBUFF_INIT(sbuff); |
2086 | | |
2087 | 0 | if (needle_len == SIZE_MAX) needle_len = strlen(needle); |
2088 | 0 | if (!needle_len) return NULL; |
2089 | | |
2090 | | /* |
2091 | | * Needle bigger than haystack |
2092 | | */ |
2093 | 0 | if (len < needle_len) return NULL; |
2094 | | |
2095 | 0 | while (total <= (len - needle_len)) { |
2096 | 0 | char const *found; |
2097 | 0 | char *end; |
2098 | | |
2099 | | /* |
2100 | | * If the needle is longer than |
2101 | | * the remaining buffer, return. |
2102 | | */ |
2103 | 0 | if (fr_sbuff_extend_lowat(NULL, &our_sbuff, needle_len) < needle_len) break; |
2104 | | |
2105 | 0 | end = CONSTRAINED_END(&our_sbuff, len, total); |
2106 | 0 | found = memmem(our_sbuff.p, end - our_sbuff.p, needle, needle_len); |
2107 | 0 | if (found) { |
2108 | 0 | (void)fr_sbuff_set(sbuff, found); |
2109 | 0 | return sbuff->p; |
2110 | 0 | } |
2111 | | |
2112 | | /* |
2113 | | * Partial needle may be in |
2114 | | * the end of the buffer so |
2115 | | * don't advance too far. |
2116 | | */ |
2117 | 0 | total += fr_sbuff_set(&our_sbuff, (end - needle_len) + 1); |
2118 | 0 | } |
2119 | | |
2120 | 0 | return NULL; |
2121 | 0 | } |
2122 | | |
2123 | | /** Wind position to the first instance of the specified needle |
2124 | | * |
2125 | | * @param[in,out] sbuff sbuff to search in. |
2126 | | * @param[in] len Maximum amount to advance by. Unconstrained if SIZE_MAX. |
2127 | | * @param[in] needle to search for. |
2128 | | * @param[in] needle_len Length of the needle. SIZE_MAX to used strlen. |
2129 | | * @return |
2130 | | * - NULL, no instances found. |
2131 | | * - The position of the first character. |
2132 | | */ |
2133 | | char *fr_sbuff_adv_to_strcase(fr_sbuff_t *sbuff, size_t len, char const *needle, size_t needle_len) |
2134 | 0 | { |
2135 | 0 | fr_sbuff_t our_sbuff = FR_SBUFF(sbuff); |
2136 | 0 | size_t total = 0; |
2137 | |
|
2138 | 0 | CHECK_SBUFF_INIT(sbuff); |
2139 | | |
2140 | 0 | if (needle_len == SIZE_MAX) needle_len = strlen(needle); |
2141 | 0 | if (!needle_len) return NULL; |
2142 | | |
2143 | | /* |
2144 | | * Needle bigger than haystack |
2145 | | */ |
2146 | 0 | if (len < needle_len) return NULL; |
2147 | | |
2148 | 0 | while (total <= (len - needle_len)) { |
2149 | 0 | char *p, *end; |
2150 | 0 | char const *n_p; |
2151 | |
|
2152 | 0 | if (fr_sbuff_extend_lowat(NULL, &our_sbuff, needle_len) < needle_len) break; |
2153 | | |
2154 | 0 | for (p = our_sbuff.p, n_p = needle, end = our_sbuff.p + needle_len; |
2155 | 0 | (p < end) && (tolower((uint8_t) *p) == tolower((uint8_t) *n_p)); |
2156 | 0 | p++, n_p++); |
2157 | 0 | if (p == end) { |
2158 | 0 | (void)fr_sbuff_set(sbuff, our_sbuff.p); |
2159 | 0 | return sbuff->p; |
2160 | 0 | } |
2161 | | |
2162 | 0 | total += fr_sbuff_advance(&our_sbuff, 1); |
2163 | 0 | } |
2164 | | |
2165 | 0 | return NULL; |
2166 | 0 | } |
2167 | | |
2168 | | /** Return true if the current char matches, and if it does, advance |
2169 | | * |
2170 | | * @param[in] sbuff to search for char in. |
2171 | | * @param[in] c char to search for. |
2172 | | * @return |
2173 | | * - true and advance if the next character matches. |
2174 | | * - false and don't advance if the next character doesn't match. |
2175 | | */ |
2176 | | bool fr_sbuff_next_if_char(fr_sbuff_t *sbuff, char c) |
2177 | 350k | { |
2178 | 350k | CHECK_SBUFF_INIT(sbuff); |
2179 | | |
2180 | 350k | if (!fr_sbuff_extend(sbuff)) return false; |
2181 | | |
2182 | 334k | if (*sbuff->p != c) return false; |
2183 | | |
2184 | 64.2k | fr_sbuff_advance(sbuff, 1); |
2185 | | |
2186 | 64.2k | return true; |
2187 | 334k | } |
2188 | | |
2189 | | /** Return true and advance if the next char does not match |
2190 | | * |
2191 | | * @param[in] sbuff to search for char in. |
2192 | | * @param[in] c char to search for. |
2193 | | * @return |
2194 | | * - true and advance unless the character matches. |
2195 | | * - false and don't advance if the next character matches. |
2196 | | */ |
2197 | | bool fr_sbuff_next_unless_char(fr_sbuff_t *sbuff, char c) |
2198 | 0 | { |
2199 | 0 | CHECK_SBUFF_INIT(sbuff); |
2200 | | |
2201 | 0 | if (!fr_sbuff_extend(sbuff)) return false; |
2202 | | |
2203 | 0 | if (*sbuff->p == c) return false; |
2204 | | |
2205 | 0 | fr_sbuff_advance(sbuff, 1); |
2206 | |
|
2207 | 0 | return true; |
2208 | 0 | } |
2209 | | |
2210 | | /** Trim trailing characters from a string we're composing |
2211 | | * |
2212 | | * @param[in] sbuff to trim trailing characters from. |
2213 | | * @param[in] to_trim Charset to trim. |
2214 | | * @return how many chars we removed. |
2215 | | */ |
2216 | | size_t fr_sbuff_trim(fr_sbuff_t *sbuff, bool const to_trim[static SBUFF_CHAR_CLASS]) |
2217 | 0 | { |
2218 | 0 | char *p = sbuff->p - 1; |
2219 | 0 | ssize_t slen; |
2220 | |
|
2221 | 0 | while ((p >= sbuff->start) && to_trim[(uint8_t)*p]) p--; |
2222 | |
|
2223 | 0 | slen = fr_sbuff_set(sbuff, p + 1); |
2224 | 0 | if (slen != 0) fr_sbuff_terminate(sbuff); |
2225 | |
|
2226 | 0 | return slen; |
2227 | 0 | } |
2228 | | |
2229 | | /** Efficient terminal string search |
2230 | | * |
2231 | | * Caller should ensure that a buffer extension of needle_len bytes has been requested |
2232 | | * before calling this function. |
2233 | | * |
2234 | | * @param[in] in Sbuff to search in. |
2235 | | * @param[in] tt Token terminals in the encompassing grammar. |
2236 | | * @return |
2237 | | * - true if found. |
2238 | | * - false if not. |
2239 | | */ |
2240 | | bool fr_sbuff_is_terminal(fr_sbuff_t *in, fr_sbuff_term_t const *tt) |
2241 | 42.8k | { |
2242 | 42.8k | uint8_t idx[SBUFF_CHAR_CLASS]; /* Fast path index */ |
2243 | 42.8k | size_t needle_len = 1; |
2244 | | |
2245 | | /* |
2246 | | * No terminal, check for EOF. |
2247 | | */ |
2248 | 42.8k | if (!tt) { |
2249 | 2.90k | fr_sbuff_extend_status_t status = 0; |
2250 | | |
2251 | 2.90k | if ((fr_sbuff_extend_lowat(&status, in, 1) == 0) && |
2252 | 992 | (status & FR_SBUFF_FLAG_EXTEND_ERROR) == 0) { |
2253 | 992 | return true; |
2254 | 992 | } |
2255 | | |
2256 | 1.91k | return false; |
2257 | 2.90k | } |
2258 | | |
2259 | | /* |
2260 | | * Initialise the fastpath index and |
2261 | | * figure out the longest needle. |
2262 | | */ |
2263 | 39.9k | fr_sbuff_terminal_idx_init(&needle_len, idx, tt); |
2264 | | |
2265 | 39.9k | fr_sbuff_extend_lowat(NULL, in, needle_len); |
2266 | | |
2267 | 39.9k | return fr_sbuff_terminal_search(in, in->p, idx, tt, needle_len); |
2268 | 42.8k | } |
2269 | | |
2270 | | /** Print a char in a friendly format |
2271 | | * |
2272 | | */ |
2273 | | static char const *sbuff_print_char(char c) |
2274 | 0 | { |
2275 | 0 | static bool const unprintables[SBUFF_CHAR_CLASS] = { |
2276 | 0 | SBUFF_CHAR_UNPRINTABLES_LOW, |
2277 | 0 | SBUFF_CHAR_UNPRINTABLES_EXTENDED |
2278 | 0 | }; |
2279 | |
|
2280 | 0 | static _Thread_local char str[10][5]; |
2281 | 0 | static _Thread_local size_t i = 0; |
2282 | |
|
2283 | 0 | switch (c) { |
2284 | 0 | case '\a': |
2285 | 0 | return "\a"; |
2286 | | |
2287 | 0 | case '\b': |
2288 | 0 | return "\b"; |
2289 | | |
2290 | 0 | case '\n': |
2291 | 0 | return "\n"; |
2292 | | |
2293 | 0 | case '\r': |
2294 | 0 | return "\r"; |
2295 | | |
2296 | 0 | case '\t': |
2297 | 0 | return "\t"; |
2298 | | |
2299 | 0 | case '\f': |
2300 | 0 | return "\f"; |
2301 | | |
2302 | 0 | case '\v': |
2303 | 0 | return "\v"; |
2304 | | |
2305 | 0 | default: |
2306 | 0 | if (i >= NUM_ELEMENTS(str)) i = 0; |
2307 | |
|
2308 | 0 | if (unprintables[(uint8_t)c]) { |
2309 | 0 | snprintf(str[i], sizeof(str[i]), "\\x%02x", (uint8_t) c); |
2310 | 0 | return str[i++]; |
2311 | 0 | } |
2312 | | |
2313 | 0 | str[i][0] = c; |
2314 | 0 | str[i][1] = '\0'; |
2315 | 0 | return str[i++]; |
2316 | 0 | } |
2317 | 0 | } |
2318 | | |
2319 | | void fr_sbuff_unescape_debug(FILE *fp, fr_sbuff_unescape_rules_t const *escapes) |
2320 | 0 | { |
2321 | 0 | int i; |
2322 | |
|
2323 | 0 | fprintf(fp, "Escape rules %s (%p)\n", escapes->name, escapes); |
2324 | 0 | fprintf(fp, "chr : %c\n", escapes->chr ? escapes->chr : ' '); |
2325 | 0 | fprintf(fp, "do_hex : %s\n", escapes->do_hex ? "yes" : "no"); |
2326 | 0 | fprintf(fp, "do_oct : %s\n", escapes->do_oct ? "yes" : "no"); |
2327 | |
|
2328 | 0 | fprintf(fp, "substitutions:\n"); |
2329 | 0 | for (i = 0; i < SBUFF_CHAR_CLASS; i++) { |
2330 | 0 | if (escapes->subs[i]) FR_FAULT_LOG("\t%s -> %s\n", |
2331 | 0 | sbuff_print_char((char)i), |
2332 | 0 | sbuff_print_char((char)escapes->subs[i])); |
2333 | 0 | } |
2334 | 0 | fprintf(fp, "skips:\n"); |
2335 | 0 | for (i = 0; i < SBUFF_CHAR_CLASS; i++) { |
2336 | 0 | if (escapes->skip[i]) fprintf(fp, "\t%s\n", sbuff_print_char((char)i)); |
2337 | 0 | } |
2338 | 0 | } |
2339 | | |
2340 | | void fr_sbuff_terminal_debug(FILE *fp, fr_sbuff_term_t const *tt) |
2341 | 0 | { |
2342 | 0 | size_t i; |
2343 | |
|
2344 | 0 | fprintf(fp, "Terminal count %zu\n", tt->len); |
2345 | |
|
2346 | 0 | for (i = 0; i < tt->len; i++) fprintf(fp, "\t\"%s\" (%zu)\n", tt->elem[i].str, tt->elem[i].len); |
2347 | 0 | } |
2348 | | |
2349 | | void fr_sbuff_parse_rules_debug(FILE *fp, fr_sbuff_parse_rules_t const *p_rules) |
2350 | 0 | { |
2351 | 0 | fprintf(fp, "Parse rules %p\n", p_rules); |
2352 | |
|
2353 | 0 | FR_FAULT_LOG("Escapes - "); |
2354 | 0 | if (p_rules->escapes) { |
2355 | 0 | fr_sbuff_unescape_debug(fp, p_rules->escapes); |
2356 | 0 | } else { |
2357 | 0 | fprintf(fp, "<none>\n"); |
2358 | 0 | } |
2359 | |
|
2360 | 0 | FR_FAULT_LOG("Terminals - "); |
2361 | 0 | if (p_rules->terminals) { |
2362 | 0 | fr_sbuff_terminal_debug(fp, p_rules->terminals); |
2363 | 0 | } else { |
2364 | 0 | fprintf(fp, "<none>\n"); |
2365 | 0 | } |
2366 | 0 | } |
2367 | | |
2368 | | /** Concat an array of strings (not NULL terminated), with a string separator |
2369 | | * |
2370 | | * @param[out] out Where to write the resulting string. |
2371 | | * @param[in] array of strings to concat. |
2372 | | * @param[in] sep to insert between elements. May be NULL. |
2373 | | * @return |
2374 | | * - >= 0 on success - length of the string created. |
2375 | | * - <0 on failure. How many bytes we would need. |
2376 | | */ |
2377 | | fr_slen_t fr_sbuff_array_concat(fr_sbuff_t *out, char const * const *array, char const *sep) |
2378 | 0 | { |
2379 | 0 | fr_sbuff_t our_out = FR_SBUFF(out); |
2380 | 0 | size_t len = talloc_array_length(array); |
2381 | 0 | char const * const * p; |
2382 | 0 | char const * const * end; |
2383 | 0 | fr_sbuff_escape_rules_t e_rules = { |
2384 | 0 | .name = __FUNCTION__, |
2385 | 0 | .chr = '\\' |
2386 | 0 | }; |
2387 | |
|
2388 | 0 | if (sep) e_rules.subs[(uint8_t)*sep] = *sep; |
2389 | |
|
2390 | 0 | for (p = array, end = array + len; |
2391 | 0 | (p < end); |
2392 | 0 | p++) { |
2393 | 0 | if (*p) FR_SBUFF_RETURN(fr_sbuff_in_escape, &our_out, *p, strlen(*p), &e_rules); |
2394 | | |
2395 | 0 | if (sep && ((p + 1) < end)) { |
2396 | 0 | FR_SBUFF_RETURN(fr_sbuff_in_strcpy, &our_out, sep); |
2397 | 0 | } |
2398 | 0 | } |
2399 | | |
2400 | 0 | FR_SBUFF_SET_RETURN(out, &our_out); |
2401 | 0 | } |