Coverage Report

Created: 2026-04-12 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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: 44331d5943621b7463c5e9a0ddb1e5f705f11c11 $")
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
82.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
82.1M
#  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
44.6M
{
130
44.6M
  ssize_t diff;
131
44.6M
  size_t  i_len = i_end - i_start;
132
133
44.6M
  if (unlikely((o_end < o_start) || (i_end < i_start))) return 0; /* sanity check */
134
135
44.6M
  diff = (o_end - o_start) - (i_len);
136
44.6M
  if (diff < 0) return diff;
137
138
44.6M
  if ((i_start > o_end) || (i_end < o_start)) {     /* no-overlap */
139
44.6M
    memcpy(o_start,  i_start, i_len);
140
44.6M
  } else {             /* overlap */
141
0
    memmove(o_start, i_start, i_len);
142
0
  }
143
144
44.6M
  return (i_len);
145
44.6M
}
146
147
static inline CC_HINT(always_inline) size_t min(size_t x, size_t y)
148
235k
{
149
235k
  return x < y ? x : y;
150
235k
}
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
43.0k
{
162
43.0k
  fr_sbuff_t    *sbuff_i;
163
43.0k
  char      *old_buff;  /* Current buff */
164
165
43.0k
  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
160k
  for (sbuff_i = sbuff; sbuff_i; sbuff_i = sbuff_i->parent) {
174
117k
    fr_sbuff_marker_t *m_i;
175
176
117k
    sbuff_i->buff = new_buff;
177
117k
    sbuff_i->start = new_buff + min(new_len, sbuff_i->start - old_buff);
178
117k
    sbuff_i->end = sbuff_i->buff + new_len;
179
117k
    *(sbuff_i->end) = '\0'; /* Re-terminate */
180
181
117k
    sbuff_i->p = new_buff + min(new_len, sbuff_i->p - old_buff);
182
183
117k
    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
117k
  }
185
43.0k
}
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
37.5k
{
371
37.5k
  fr_sbuff_uctx_talloc_t  *tctx = sbuff->uctx;
372
37.5k
  size_t      clen, nlen, elen = extension;
373
37.5k
  char      *new_buff;
374
375
37.5k
  CHECK_SBUFF_INIT(sbuff);
376
377
37.5k
  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
37.5k
  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
37.5k
  } else if (elen < clen) {
392
28.6k
    elen = clen - 1;    /* Don't double alloc \0 */
393
28.6k
  }
394
395
  /*
396
   *  Check we don't exceed the maximum buffer
397
   *  length, including the NUL byte.
398
   */
399
37.5k
  if (tctx->max && ((clen + elen + 1) > tctx->max)) {
400
0
    elen = tctx->max - clen;
401
0
    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
0
    elen += 1;      /* add \0 */
408
0
  }
409
37.5k
  nlen = clen + elen;
410
411
37.5k
  new_buff = talloc_realloc(tctx->ctx, sbuff->buff, char, nlen);
412
37.5k
  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
37.5k
  (void)fr_sbuff_update(sbuff, new_buff, nlen - 1); /* Shouldn't fail as we're extending */
419
420
37.5k
  return elen;
421
37.5k
}
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
7.55k
{
435
7.55k
  size_t      clen = 0, nlen = 1;
436
7.55k
  char      *new_buff;
437
7.55k
  fr_sbuff_uctx_talloc_t  *tctx = sbuff->uctx;
438
439
7.55k
  CHECK_SBUFF_INIT(sbuff);
440
441
7.55k
  if (sbuff->buff) clen = talloc_array_length(sbuff->buff);
442
443
7.55k
  if (len != SIZE_MAX) {
444
0
    nlen += len;
445
7.55k
  } else if (sbuff->buff){
446
7.55k
    nlen += (sbuff->p - sbuff->start);
447
7.55k
  }
448
449
7.55k
  if (nlen != clen) {
450
5.46k
    new_buff = talloc_realloc(tctx->ctx, sbuff->buff, char, nlen);
451
5.46k
    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
5.46k
    fr_sbuff_update(sbuff, new_buff, nlen - 1);
456
5.46k
  }
457
458
7.55k
  return 0;
459
7.55k
}
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
18
{
470
18
  fr_sbuff_uctx_talloc_t  *tctx = sbuff->uctx;
471
472
18
  CHECK_SBUFF_INIT(sbuff);
473
474
18
  fr_sbuff_set_to_start(sbuff); /* Clear data */
475
18
  sbuff->m = NULL;   /* Remove any maker references */
476
477
18
  if (fr_sbuff_used(sbuff) != tctx->init) {
478
18
    char *new_buff;
479
480
18
    new_buff = talloc_realloc(tctx->ctx, sbuff->buff, char, tctx->init);
481
18
    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
18
    sbuff->buff = new_buff;
487
18
    fr_sbuff_update(sbuff, new_buff, tctx->init - 1);
488
18
  }
489
490
18
  return 0;
491
18
}
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
48.9k
#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
63.4k
  (((_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
929
{
523
929
  size_t i, len, max = 0;
524
525
929
  if (!term) return;
526
527
4
  memset(idx, 0, SBUFF_CHAR_CLASS);
528
529
12
  for (i = 0; i < term->len; i++) {
530
8
    len = term->elem[i].len;
531
8
    if (len > max) max = len;
532
533
8
    idx[(uint8_t)term->elem[i].str[0]] = i + 1;
534
8
  }
535
536
4
  if (i > 0) *needle_len = max;
537
4
}
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, size_t needle_len)
557
20.6M
{
558
20.6M
  uint8_t   term_idx;
559
560
20.6M
  ssize_t   start = 0;
561
20.6M
  ssize_t   end;
562
20.6M
  ssize_t   mid;
563
564
20.6M
  size_t    remaining;
565
566
20.6M
  if (!term) return false;      /* If there's no terminals, we don't need to search */
567
568
24
  end = term->len - 1;
569
570
24
  term_idx = idx[(uint8_t)*p];      /* Fast path */
571
24
  if (!term_idx) return false;
572
573
  /*
574
   *  Special case for EOFlike states
575
   */
576
0
  remaining = fr_sbuff_remaining(in);
577
0
  if ((remaining == 0) && !fr_sbuff_is_extendable(in)) {
578
0
    if (idx['\0'] != 0) return true;
579
0
    return false;
580
0
  }
581
582
0
  if (remaining < needle_len) {
583
0
    fr_assert_msg(!fr_sbuff_is_extendable(in),
584
0
            "Caller failed to extend buffer by %zu bytes before calling fr_sbuff_terminal_search",
585
0
            needle_len);
586
    /*
587
     *  We can't search for the needle if we don't have
588
     *  enough data to match it.
589
     */
590
0
    return false;
591
0
  }
592
593
0
  mid = term_idx - 1;       /* Inform the mid point from the index */
594
595
0
  while (start <= end) {
596
0
    fr_sbuff_term_elem_t const  *elem;
597
0
    size_t        tlen;
598
0
    int       ret;
599
600
0
    elem = &term->elem[mid];
601
0
    tlen = elem->len;
602
603
0
    ret = memcmp(p, elem->str, tlen < (size_t)remaining ? tlen : (size_t)remaining);
604
0
    if (ret == 0) {
605
      /*
606
       *  If we have more text than the table element, that's fine
607
       */
608
0
      if (remaining >= tlen) return true;
609
610
      /*
611
       *  If input was shorter than the table element we need to
612
       *  keep searching.
613
       */
614
0
      ret = -1;
615
0
    }
616
617
0
    if (ret < 0) {
618
0
      end = mid - 1;
619
0
    } else {
620
0
      start = mid + 1;
621
0
    }
622
623
0
    mid = start + ((end - start) / 2);  /* Avoid overflow */
624
0
  }
625
626
0
  return false;
627
0
}
628
629
/** Compare two terminal elements for ordering purposes
630
 *
631
 * @param[in] a       first terminal to compare.
632
 * @param[in] b   second terminal to compare.
633
 * @return CMP(a,b)
634
 */
635
static inline int8_t terminal_cmp(fr_sbuff_term_elem_t const *a, fr_sbuff_term_elem_t const *b)
636
0
{
637
0
  return MEMCMP_FIELDS(a, b, str, len);
638
0
}
639
640
#if 0
641
static void fr_sbuff_terminal_debug_tmp(fr_sbuff_term_elem_t const *elem[], size_t len)
642
{
643
  size_t i;
644
645
  FR_FAULT_LOG("Terminal count %zu", len);
646
647
  for (i = 0; i < len; i++) FR_FAULT_LOG("\t\"%s\" (%zu)", elem[i] ? elem[i]->str : "NULL", elem[i] ? elem[i]->len : 0);
648
}
649
#endif
650
651
/** Merge two sets of terminal strings
652
 *
653
 * @param[in] ctx to allocate the new terminal array in.
654
 * @param[in] a   first set of terminals to merge.
655
 * @param[in] b   second set of terminals to merge.
656
 * @return A new set of de-duplicated and sorted terminals.
657
 */
658
fr_sbuff_term_t *fr_sbuff_terminals_amerge(TALLOC_CTX *ctx, fr_sbuff_term_t const *a, fr_sbuff_term_t const *b)
659
0
{
660
0
  size_t        i, j, num;
661
0
  fr_sbuff_term_t     *out;
662
0
  fr_sbuff_term_elem_t const  *tmp[SBUFF_CHAR_CLASS];
663
664
  /*
665
   *  Check all inputs are pre-sorted.  It doesn't break this
666
   *  function, but it's useful in case the terminal arrays
667
   *  are defined elsewhere without merging.
668
   */
669
0
#if !defined(NDEBUG) && defined(WITH_VERIFY_PTR)
670
0
  if (a->len) for (i = 0; i < a->len - 1; i++) fr_assert(terminal_cmp(&a->elem[i], &a->elem[i + 1]) < 0);
671
0
  if (b->len) for (i = 0; i < b->len - 1; i++) fr_assert(terminal_cmp(&b->elem[i], &b->elem[i + 1]) < 0);
672
0
#endif
673
674
  /*
675
   *  Since the inputs are sorted, we can just do an O(n+m)
676
   *  walk through the arrays, comparing entries across the
677
   *  two arrays.
678
   *
679
   *  If there are duplicates, we prefer "a", for no particular reason.
680
   */
681
0
  num = i = j = 0;
682
0
  while ((i < a->len) && (j < b->len)) {
683
0
    int8_t cmp;
684
685
0
    cmp = terminal_cmp(&a->elem[i], &b->elem[j]);
686
0
    if (cmp == 0) {
687
0
      j++;
688
0
      tmp[num++] = &a->elem[i++];
689
690
0
    } else if (cmp < 0) {
691
0
      tmp[num++] = &a->elem[i++];
692
693
0
    } else if (cmp > 0) {
694
0
      tmp[num++] = &b->elem[j++];
695
0
    }
696
697
0
    fr_assert(num < SBUFF_CHAR_CLASS);
698
0
  }
699
700
  /*
701
   *  Only one of these will be hit, and it's simpler than nested "if" statements.
702
   */
703
0
  while (i < a->len) tmp[num++] = &a->elem[i++];
704
0
  while (j < b->len) tmp[num++] = &b->elem[j++];
705
706
0
  out = talloc_pooled_object(ctx, fr_sbuff_term_t, num, num * sizeof(fr_sbuff_term_elem_t));
707
0
  if (unlikely(!out)) return NULL;
708
709
0
  out->elem = talloc_array(out, fr_sbuff_term_elem_t, num);
710
0
  if (unlikely(!out->elem)) {
711
0
    talloc_free(out);
712
0
    return NULL;
713
0
  }
714
0
  out->len = num;
715
716
0
  for (i = 0; i < num; i++) out->elem[i] = *tmp[i]; /* copy merged results back */
717
718
0
#if !defined(NDEBUG) && defined(WITH_VERIFY_PTR)
719
0
  for (i = 0; i < num - 1; i++) fr_assert(terminal_cmp(&out->elem[i], &out->elem[i + 1]) < 0);
720
0
#endif
721
722
0
  return out;
723
0
}
724
725
/** Copy as many bytes as possible from a sbuff to a sbuff
726
 *
727
 * Copy size is limited by available data in sbuff and space in output sbuff.
728
 *
729
 * @param[out] out  Where to copy to.
730
 * @param[in] in  Where to copy from.  Will copy len bytes from current position in buffer.
731
 * @param[in] len How many bytes to copy.  If SIZE_MAX the entire buffer will be copied.
732
 * @return
733
 *  - 0 no bytes copied.
734
 *  - >0 the number of bytes copied.
735
 */
736
size_t fr_sbuff_out_bstrncpy(fr_sbuff_t *out, fr_sbuff_t *in, size_t len)
737
40.0k
{
738
40.0k
  fr_sbuff_t  our_in = FR_SBUFF_BIND_CURRENT(in);
739
40.0k
  size_t    remaining;
740
741
40.0k
  CHECK_SBUFF_INIT(in);
742
743
79.9k
  while (fr_sbuff_used_total(&our_in) < len) {
744
73.0k
    size_t chunk_len;
745
746
73.0k
    remaining = (len - fr_sbuff_used_total(&our_in));
747
748
73.0k
    if (!fr_sbuff_extend(&our_in)) break;
749
750
39.9k
    chunk_len = fr_sbuff_remaining(&our_in);
751
39.9k
    if (chunk_len > remaining) chunk_len = remaining;
752
753
39.9k
    FILL_OR_GOTO_DONE(out, &our_in, chunk_len);
754
39.9k
  }
755
756
40.0k
done:
757
40.0k
  *out->p = '\0';
758
40.0k
  return fr_sbuff_used_total(&our_in);
759
40.0k
}
760
761
/** Copy exactly len bytes from a sbuff to a sbuff or fail
762
 *
763
 * Copy size is limited by available data in sbuff, space in output sbuff, and length.
764
 *
765
 * @param[out] out  Where to copy to.
766
 * @param[in] in  Where to copy from.  Will copy len bytes from current position in buffer.
767
 * @param[in] len How many bytes to copy.  If SIZE_MAX the entire buffer will be copied.
768
 * @return
769
 *  - 0 no bytes copied, no token found of sufficient length in input buffer.
770
 *  - >0 the number of bytes copied.
771
 *  - <0 the number of additional output bytes we would have needed to
772
 *    complete the copy.
773
 */
774
ssize_t fr_sbuff_out_bstrncpy_exact(fr_sbuff_t *out, fr_sbuff_t *in, size_t len)
775
0
{
776
0
  fr_sbuff_t    our_in = FR_SBUFF(in);
777
0
  size_t      remaining;
778
0
  fr_sbuff_marker_t m;
779
780
0
  CHECK_SBUFF_INIT(in);
781
782
0
  fr_sbuff_marker(&m, out);
783
784
0
  do {
785
0
    size_t chunk_len;
786
0
    ssize_t copied;
787
788
0
    remaining = (len - fr_sbuff_used_total(&our_in));
789
0
    if (remaining && !fr_sbuff_extend(&our_in)) {
790
0
      fr_sbuff_marker_release(&m);
791
0
      return 0;
792
0
    }
793
794
0
    chunk_len = fr_sbuff_remaining(&our_in);
795
0
    if (chunk_len > remaining) chunk_len = remaining;
796
797
0
    copied = fr_sbuff_in_bstrncpy(out, our_in.p, chunk_len);
798
0
    if (copied < 0) {
799
0
      fr_sbuff_set(out, &m);    /* Reset out */
800
0
      *m.p = '\0';      /* Re-terminate */
801
802
      /* Amount remaining in input buffer minus the amount we could have copied */
803
0
      if (len == SIZE_MAX) {
804
0
        fr_sbuff_marker_release(&m);
805
0
        return -(fr_sbuff_remaining(in) - (chunk_len + copied));
806
0
      }
807
      /* Amount remaining to copy minus the amount we could have copied */
808
0
      fr_sbuff_marker_release(&m);
809
0
      return -(remaining - (chunk_len + copied));
810
0
    }
811
0
    fr_sbuff_advance(&our_in, copied);
812
0
  } while (fr_sbuff_used_total(&our_in) < len);
813
814
0
  fr_sbuff_marker_release(&m);
815
816
0
  FR_SBUFF_SET_RETURN(in, &our_in);  /* in was pinned, so this works */
817
0
}
818
819
/** Copy as many allowed characters as possible from a sbuff to a sbuff
820
 *
821
 * Copy size is limited by available data in sbuff and output buffer length.
822
 *
823
 * As soon as a disallowed character is found the copy is stopped.
824
 * The input sbuff will be left pointing at the first disallowed character.
825
 *
826
 * @param[out] out    Where to copy to.
827
 * @param[in] in    Where to copy from.  Will copy len bytes from current position in buffer.
828
 * @param[in] len   How many bytes to copy.  If SIZE_MAX the entire buffer will be copied.
829
 * @param[in] allowed   Characters to include the copy.
830
 * @return
831
 *  - 0 no bytes copied.
832
 *  - >0 the number of bytes copied.
833
 */
834
size_t fr_sbuff_out_bstrncpy_allowed(fr_sbuff_t *out, fr_sbuff_t *in, size_t len,
835
             bool const allowed[static SBUFF_CHAR_CLASS])
836
8.82k
{
837
8.82k
  fr_sbuff_t  our_in = FR_SBUFF_BIND_CURRENT(in);
838
839
8.82k
  CHECK_SBUFF_INIT(in);
840
841
16.1k
  while (fr_sbuff_used_total(&our_in) < len) {
842
16.1k
    char  *p;
843
16.1k
    char  *end;
844
845
16.1k
    if (!fr_sbuff_extend(&our_in)) break;
846
847
8.83k
    p = fr_sbuff_current(&our_in);
848
8.83k
    end = CONSTRAINED_END(&our_in, len, fr_sbuff_used_total(&our_in));
849
850
3.84M
    while ((p < end) && allowed[(uint8_t)*p]) p++;
851
852
8.83k
    FILL_OR_GOTO_DONE(out, &our_in, p - our_in.p);
853
854
8.77k
    if (p != end) break;   /* stopped early, break */
855
8.77k
  }
856
857
8.82k
done:
858
8.82k
  *out->p = '\0';
859
8.82k
  return fr_sbuff_used_total(&our_in);
860
8.82k
}
861
862
/** Copy as many allowed characters as possible from a sbuff to a sbuff
863
 *
864
 * Copy size is limited by available data in sbuff and output buffer length.
865
 *
866
 * As soon as a disallowed character is found the copy is stopped.
867
 * The input sbuff will be left pointing at the first disallowed character.
868
 *
869
 * @param[out] out    Where to copy to.
870
 * @param[in] in    Where to copy from.  Will copy len bytes from current position in buffer.
871
 * @param[in] len   How many bytes to copy.  If SIZE_MAX the entire buffer will be copied.
872
 * @param[in] tt    Token terminals in the encompassing grammar.
873
 * @param[in] u_rules   If not NULL, ignore characters in the until set when
874
 *        prefixed with u_rules->chr. FIXME - Should actually evaluate
875
 *        u_rules fully.
876
 * @return
877
 *  - 0 no bytes copied.
878
 *  - >0 the number of bytes copied.
879
 */
880
size_t fr_sbuff_out_bstrncpy_until(fr_sbuff_t *out, fr_sbuff_t *in, size_t len,
881
           fr_sbuff_term_t const *tt,
882
           fr_sbuff_unescape_rules_t const *u_rules)
883
182
{
884
182
  fr_sbuff_t  our_in = FR_SBUFF_BIND_CURRENT(in);
885
182
  bool    do_escape = false;    /* Track state across extensions */
886
887
182
  uint8_t   idx[SBUFF_CHAR_CLASS];    /* Fast path index */
888
182
  size_t    needle_len = 1;
889
182
  char    escape_chr = u_rules ? u_rules->chr : '\0';
890
891
182
  CHECK_SBUFF_INIT(in);
892
893
  /*
894
   *  Initialise the fastpath index and
895
   *  figure out the longest needle.
896
   */
897
182
  fr_sbuff_terminal_idx_init(&needle_len, idx, tt);
898
899
363
  while (fr_sbuff_used_total(&our_in) < len) {
900
363
    char  *p;
901
363
    char  *end;
902
903
363
    if (fr_sbuff_extend_lowat(NULL, &our_in, needle_len) == 0) break;
904
905
181
    p = fr_sbuff_current(&our_in);
906
181
    end = CONSTRAINED_END(&our_in, len, fr_sbuff_used_total(&our_in));
907
908
181
    if (p == end) break;
909
910
181
    if (escape_chr == '\0') {
911
20.6M
      while ((p < end) && !fr_sbuff_terminal_search(in, p, idx, tt, needle_len)) p++;
912
181
    } else {
913
0
      while (p < end) {
914
0
        if (do_escape) {
915
0
          do_escape = false;
916
0
        } else if (*p == escape_chr) {
917
0
          do_escape = true;
918
0
        } else if (fr_sbuff_terminal_search(in, p, idx, tt, needle_len)) {
919
0
          break;
920
0
        }
921
0
        p++;
922
0
      }
923
0
    }
924
925
181
    FILL_OR_GOTO_DONE(out, &our_in, p - our_in.p);
926
927
181
    if (p != end) break;   /* stopped early, break */
928
181
  }
929
930
182
done:
931
182
  *out->p = '\0';
932
182
  return fr_sbuff_used_total(&our_in);
933
182
}
934
935
/** Copy as many allowed characters as possible from a sbuff to a sbuff
936
 *
937
 * Copy size is limited by available data in sbuff and output buffer length.
938
 *
939
 * As soon as a disallowed character is found the copy is stopped.
940
 * The input sbuff will be left pointing at the first disallowed character.
941
 *
942
 * This de-escapes characters as they're copied out of the sbuff.
943
 *
944
 * @param[out] out    Where to copy to.
945
 * @param[in] in    Where to copy from.  Will copy len bytes from current position in buffer.
946
 * @param[in] len   How many bytes to copy.  If SIZE_MAX the entire buffer will be copied.
947
 * @param[in] tt    Token terminal strings in the encompassing grammar.
948
 * @param[in] u_rules   for processing unescape sequences.
949
 * @return
950
 *  - 0 no bytes copied.
951
 *  - >0 the number of bytes written to out.
952
 */
953
size_t fr_sbuff_out_unescape_until(fr_sbuff_t *out, fr_sbuff_t *in, size_t len,
954
           fr_sbuff_term_t const *tt,
955
           fr_sbuff_unescape_rules_t const *u_rules)
956
178
{
957
178
  fr_sbuff_t      our_in;
958
178
  bool        do_escape = false;      /* Track state across extensions */
959
178
  fr_sbuff_marker_t   o_s;
960
178
  fr_sbuff_marker_t   c_s;
961
178
  fr_sbuff_marker_t   end;
962
963
178
  uint8_t       idx[SBUFF_CHAR_CLASS];      /* Fast path index */
964
178
  size_t        needle_len = 1;
965
178
  fr_sbuff_extend_status_t  status = 0;
966
967
  /*
968
   *  If we don't need to do unescaping
969
   *  call a more suitable function.
970
   */
971
178
  if (!u_rules || (u_rules->chr == '\0')) return fr_sbuff_out_bstrncpy_until(out, in, len, tt, u_rules);
972
973
0
  CHECK_SBUFF_INIT(in);
974
975
0
  our_in = FR_SBUFF(in);
976
977
  /*
978
   *  Chunk tracking...
979
   */
980
0
  fr_sbuff_marker(&c_s, &our_in);
981
0
  fr_sbuff_marker(&end, &our_in);
982
0
  fr_sbuff_marker_update_end(&end, len);
983
984
0
  fr_sbuff_marker(&o_s, out);
985
986
  /*
987
   *  Initialise the fastpath index and
988
   *  figure out the longest needle.
989
   */
990
0
  fr_sbuff_terminal_idx_init(&needle_len, idx, tt);
991
992
  /*
993
   *  ...while we have remaining data
994
   */
995
0
  while (fr_sbuff_extend_lowat(&status, &our_in, needle_len) > 0) {
996
0
    if (fr_sbuff_was_extended(status)) fr_sbuff_marker_update_end(&end, len);
997
0
    if (!fr_sbuff_diff(&our_in, &end)) break; /* Reached the end */
998
999
0
    if (do_escape) {
1000
0
      do_escape = false;
1001
1002
      /*
1003
       *  Check for \x<hex><hex>
1004
       */
1005
0
      if (u_rules->do_hex && fr_sbuff_is_char(&our_in, 'x')) {
1006
0
        uint8_t     escape;
1007
0
        fr_sbuff_marker_t m;
1008
1009
0
        fr_sbuff_marker(&m, &our_in);   /* allow for backtrack */
1010
0
        fr_sbuff_advance(&our_in, 1);   /* skip over the 'x' */
1011
1012
0
        if (fr_sbuff_out_uint8_hex(NULL, &escape, &our_in, false) != 2) {
1013
0
          fr_sbuff_set(&our_in, &m);  /* backtrack */
1014
0
          fr_sbuff_marker_release(&m);
1015
0
          goto check_subs;    /* allow sub for \x */
1016
0
        }
1017
1018
0
        if (fr_sbuff_in_char(out, escape) <= 0) {
1019
0
          fr_sbuff_set(&our_in, &m);  /* backtrack */
1020
0
          fr_sbuff_marker_release(&m);
1021
0
          break;
1022
0
        }
1023
0
        fr_sbuff_marker_release(&m);
1024
0
        fr_sbuff_set(&c_s, &our_in);
1025
0
        continue;
1026
0
      }
1027
1028
      /*
1029
       *  Check for \<oct><oct><oct>
1030
       */
1031
0
      if (u_rules->do_oct && fr_sbuff_is_digit(&our_in)) {
1032
0
        uint8_t     escape;
1033
0
        fr_sbuff_marker_t m;
1034
1035
0
        fr_sbuff_marker(&m, &our_in);   /* allow for backtrack */
1036
1037
0
        if (fr_sbuff_out_uint8_oct(NULL, &escape, &our_in, false) != 3) {
1038
0
          fr_sbuff_set(&our_in, &m);  /* backtrack */
1039
0
          fr_sbuff_marker_release(&m);
1040
0
          goto check_subs;    /* allow sub for \<oct> */
1041
0
        }
1042
1043
0
        if (fr_sbuff_in_char(out, escape) <= 0) {
1044
0
          fr_sbuff_set(&our_in, &m);  /* backtrack */
1045
0
          fr_sbuff_marker_release(&m);
1046
0
          break;
1047
0
        }
1048
0
        fr_sbuff_marker_release(&m);
1049
0
        fr_sbuff_set(&c_s, &our_in);
1050
0
        continue;
1051
0
      }
1052
1053
0
    check_subs:
1054
      /*
1055
       *  Not a recognised hex or octal escape sequence
1056
       *  may be a substitution or a sequence that
1057
       *  should be copied to the output buffer.
1058
       */
1059
0
      {
1060
0
        uint8_t c = *fr_sbuff_current(&our_in);
1061
1062
0
        if (u_rules->subs[c] == '\0') {
1063
0
          if (u_rules->skip[c] == true) goto next;
1064
0
          goto next_esc;
1065
0
        }
1066
1067
        /*
1068
         *    We already copied everything up
1069
         *  to this point, so we can now
1070
         *  write the substituted char to
1071
         *  the output buffer.
1072
         */
1073
0
        if (fr_sbuff_in_char(out, u_rules->subs[c]) <= 0) break;
1074
1075
        /*
1076
         *  ...and advance past the entire
1077
         *  escape seq in the input buffer.
1078
         */
1079
0
        fr_sbuff_advance(&our_in, 1);
1080
0
        fr_sbuff_set(&c_s, &our_in);
1081
0
        continue;
1082
0
      }
1083
0
    }
1084
1085
0
  next_esc:
1086
0
    if (*fr_sbuff_current(&our_in) == u_rules->chr) {
1087
      /*
1088
       *  Copy out any data we got before
1089
       *  we hit the escape char.
1090
       *
1091
       *  We need to do this before we
1092
       *  can write the escape char to
1093
       *  the output sbuff.
1094
       */
1095
0
      FILL_OR_GOTO_DONE(out, &c_s, fr_sbuff_behind(&c_s));
1096
1097
0
      do_escape = true;
1098
0
      fr_sbuff_advance(&our_in, 1);
1099
0
      continue;
1100
0
    }
1101
1102
0
  next:
1103
0
    if (tt && fr_sbuff_terminal_search(&our_in, fr_sbuff_current(&our_in), idx, tt, needle_len)) break;
1104
0
    fr_sbuff_advance(&our_in, 1);
1105
0
  }
1106
1107
  /*
1108
   *  Copy any remaining data over
1109
   */
1110
0
  FILL_OR_GOTO_DONE(out, &c_s, fr_sbuff_behind(&c_s));
1111
1112
0
done:
1113
0
  fr_sbuff_set(in, &c_s); /* Only advance by as much as we copied */
1114
0
  *out->p = '\0';
1115
1116
0
  return fr_sbuff_marker_release_behind(&o_s);
1117
0
}
1118
1119
/** See if the string contains a truth value
1120
 *
1121
 * @param[out] out  Where to write boolean value.
1122
 * @param[in] in  Where to search for a truth value.
1123
 * @return
1124
 *  - >0 the number of bytes consumed.
1125
 *  - -1 no bytes copied, was not a truth value.
1126
 */
1127
fr_slen_t fr_sbuff_out_bool(bool *out, fr_sbuff_t *in)
1128
311
{
1129
311
  fr_sbuff_t our_in = FR_SBUFF(in);
1130
1131
311
  static bool const bool_prefix[SBUFF_CHAR_CLASS] = {
1132
311
    ['t'] = true, ['T'] = true, /* true */
1133
311
    ['f'] = true, ['F'] = true, /* false */
1134
311
    ['y'] = true, ['Y'] = true, /* yes */
1135
311
    ['n'] = true, ['N'] = true, /* no */
1136
311
  };
1137
1138
311
  if (fr_sbuff_is_in_charset(&our_in, bool_prefix)) {
1139
21
    switch (tolower(fr_sbuff_char(&our_in, '\0'))) {
1140
0
    default:
1141
0
      break;
1142
1143
3
    case 't':
1144
3
      if (fr_sbuff_adv_past_strcase_literal(&our_in, "true")) {
1145
1
        *out = true;
1146
1
        FR_SBUFF_SET_RETURN(in, &our_in);
1147
1
      }
1148
2
      break;
1149
1150
9
    case 'f':
1151
9
      if (fr_sbuff_adv_past_strcase_literal(&our_in, "false")) {
1152
5
        *out = false;
1153
5
        FR_SBUFF_SET_RETURN(in, &our_in);
1154
5
      }
1155
4
      break;
1156
1157
5
    case 'y':
1158
5
      if (fr_sbuff_adv_past_strcase_literal(&our_in, "yes")) {
1159
2
        *out = true;
1160
2
        FR_SBUFF_SET_RETURN(in, &our_in);
1161
2
      }
1162
3
      break;
1163
1164
4
    case 'n':
1165
4
      if (fr_sbuff_adv_past_strcase_literal(&our_in, "no")) {
1166
1
        *out = false;
1167
1
        FR_SBUFF_SET_RETURN(in, &our_in);
1168
1
      }
1169
3
      break;
1170
21
    }
1171
21
  }
1172
1173
302
  *out = false; /* Always initialise out */
1174
1175
302
  fr_strerror_const("Not a valid boolean value.  Accepted values are 'yes', 'no', 'true', 'false'");
1176
1177
302
  return -1;
1178
311
}
1179
1180
/** Used to define a number parsing functions for signed integers
1181
 *
1182
 * @param[in] _name Function suffix.
1183
 * @param[in] _type Output type.
1184
 * @param[in] _min  value.
1185
 * @param[in] _max  value.
1186
 * @param[in] _max_char Maximum digits that can be used to represent an integer.
1187
 *      Can't use stringify because of width modifiers like 'u'
1188
 *      used in <stdint.h>.
1189
 * @param[in] _base to use.
1190
 */
1191
#define SBUFF_PARSE_INT_DEF(_name, _type, _min, _max, _max_char, _base) \
1192
7.16k
fr_slen_t fr_sbuff_out_##_name(fr_sbuff_parse_error_t *err, _type *out, fr_sbuff_t *in, bool no_trailing) \
1193
7.16k
{ \
1194
7.16k
  char    buff[_max_char + 1]; \
1195
7.16k
  char    *end, *a_end; \
1196
7.16k
  size_t    len; \
1197
7.16k
  long long num; \
1198
7.16k
  _type   cast_num; \
1199
7.16k
  fr_sbuff_t  our_in = FR_SBUFF(in); \
1200
7.16k
  buff[0] = '\0'; /* clang scan */ \
1201
7.16k
  len = fr_sbuff_out_bstrncpy(&FR_SBUFF_IN(buff, sizeof(buff)), &our_in, _max_char); \
1202
7.16k
  if (len == 0) { \
1203
6
    if (err) *err = (fr_sbuff_remaining(in) == 0) ? FR_SBUFF_PARSE_ERROR_INPUT_EMPTY : FR_SBUFF_PARSE_ERROR_NOT_FOUND; \
1204
6
    return -1; \
1205
6
  } \
1206
7.16k
  errno = 0; /* this is needed as strtoll doesn't reset errno */ \
1207
7.15k
  num = strtoll(buff, &end, _base); \
1208
7.15k
  cast_num = (_type)(num); \
1209
7.15k
  if (end == buff) { \
1210
188
    if (err) *err = FR_SBUFF_PARSE_ERROR_NOT_FOUND; \
1211
188
    return -1; \
1212
188
  } \
1213
7.15k
  if (num > cast_num) { \
1214
37
  overflow: \
1215
37
    if (err) *err = FR_SBUFF_PARSE_ERROR_NUM_OVERFLOW; \
1216
37
    *out = (_type)(_max); \
1217
37
    return -1; \
1218
20
  } \
1219
6.96k
  if (((errno == EINVAL) && (num == 0)) || ((errno == ERANGE) && (num == LLONG_MAX))) goto overflow; \
1220
6.94k
  if (num < cast_num) { \
1221
47
  underflow: \
1222
47
    if (err) *err = FR_SBUFF_PARSE_ERROR_NUM_UNDERFLOW; \
1223
47
    *out = (_type)(_min); \
1224
47
    return -1; \
1225
10
  } \
1226
6.92k
  if ((errno == ERANGE) && (num == LLONG_MIN)) goto underflow; \
1227
6.91k
  if (no_trailing && ((a_end = in->p + (end - buff)) < in->end)) { \
1228
6.53k
    if (isdigit((uint8_t) *a_end) || (((_base > 10) || ((_base == 0) && (len > 2) && (buff[0] == '0') && (buff[1] == 'x'))) && \
1229
6.42k
        ((tolower((uint8_t) *a_end) >= 'a') && (tolower((uint8_t) *a_end) <= 'f')))) { \
1230
133
      if (err) *err = FR_SBUFF_PARSE_ERROR_TRAILING; \
1231
133
      *out = (_type)(_max); \
1232
133
      FR_SBUFF_ERROR_RETURN(&our_in); \
1233
133
    } \
1234
6.53k
    *out = cast_num; \
1235
6.40k
  } else { \
1236
348
    if (err) *err = FR_SBUFF_PARSE_OK; \
1237
348
    *out = cast_num; \
1238
348
  } \
1239
6.88k
  return fr_sbuff_advance(in, end - buff); /* Advance by the length strtoll gives us */ \
1240
6.88k
}
1241
1242
103
SBUFF_PARSE_INT_DEF(int8, int8_t, INT8_MIN, INT8_MAX, 4, 0)
1243
100
SBUFF_PARSE_INT_DEF(int16, int16_t, INT16_MIN, INT16_MAX, 6, 0)
1244
83
SBUFF_PARSE_INT_DEF(int32, int32_t, INT32_MIN, INT32_MAX, 11, 0)
1245
6.87k
SBUFF_PARSE_INT_DEF(int64, int64_t, INT64_MIN, INT64_MAX, 20, 0)
1246
0
SBUFF_PARSE_INT_DEF(ssize, ssize_t, SSIZE_MIN, SSIZE_MAX, 20, 0)
1247
1248
/** Used to define a number parsing functions for signed integers
1249
 *
1250
 * @param[in] _name Function suffix.
1251
 * @param[in] _type Output type.
1252
 * @param[in] _max  value.
1253
 * @param[in] _max_char Maximum digits that can be used to represent an integer.
1254
 *      Can't use stringify because of width modifiers like 'u'
1255
 *      used in <stdint.h>.
1256
 * @param[in] _base of the number being parsed, 8, 10, 16 etc...
1257
 */
1258
#define SBUFF_PARSE_UINT_DEF(_name, _type, _max, _max_char, _base) \
1259
32.8k
fr_slen_t fr_sbuff_out_##_name(fr_sbuff_parse_error_t *err, _type *out, fr_sbuff_t *in, bool no_trailing) \
1260
32.8k
{ \
1261
32.8k
  char      buff[_max_char + 1]; \
1262
32.8k
  char      *end, *a_end; \
1263
32.8k
  size_t      len; \
1264
32.8k
  unsigned long long  num; \
1265
32.8k
  _type     cast_num; \
1266
32.8k
  fr_sbuff_t    our_in = FR_SBUFF(in); \
1267
32.8k
  buff[0] = '\0'; /* clang scan */ \
1268
32.8k
  len = fr_sbuff_out_bstrncpy(&FR_SBUFF_IN(buff, sizeof(buff)), &our_in, _max_char); \
1269
32.8k
  if (len == 0) { \
1270
117
    if (err) *err = (fr_sbuff_remaining(in) == 0) ? FR_SBUFF_PARSE_ERROR_INPUT_EMPTY : FR_SBUFF_PARSE_ERROR_NOT_FOUND; \
1271
117
    return -1; \
1272
117
  } \
1273
32.8k
  if (buff[0] == '-') { \
1274
716
    if (err) *err = FR_SBUFF_PARSE_ERROR_NUM_UNDERFLOW; \
1275
716
    return -1; \
1276
716
  } \
1277
32.7k
  errno = 0; /* this is needed as strtoull doesn't reset errno */ \
1278
32.0k
  num = strtoull(buff, &end, _base); \
1279
32.0k
  cast_num = (_type)(num); \
1280
32.0k
  if (end == buff) { \
1281
1.83k
    if (err) *err = FR_SBUFF_PARSE_ERROR_NOT_FOUND; \
1282
1.83k
    return -1; \
1283
1.83k
  } \
1284
32.0k
  if (num > cast_num) { \
1285
359
  overflow: \
1286
359
    if (err) *err = FR_SBUFF_PARSE_ERROR_NUM_OVERFLOW; \
1287
359
    *out = (_type)(_max); \
1288
359
    return -1; \
1289
321
  } \
1290
30.2k
  if (((errno == EINVAL) && (num == 0)) || ((errno == ERANGE) && (num == ULLONG_MAX))) goto overflow; \
1291
29.8k
  if (no_trailing && ((a_end = in->p + (end - buff)) < in->end)) { \
1292
7.33k
    if (isdigit((uint8_t) *a_end) || (((_base > 10) || ((_base == 0) && (len > 2) && (buff[0] == '0') && (buff[1] == 'x'))) && \
1293
6.82k
        ((tolower((uint8_t) *a_end) >= 'a') && (tolower((uint8_t) *a_end) <= 'f')))) { \
1294
705
      if (err) *err = FR_SBUFF_PARSE_ERROR_TRAILING; \
1295
705
      *out = (_type)(_max); \
1296
705
      FR_SBUFF_ERROR_RETURN(&our_in); \
1297
705
    } \
1298
7.33k
    if (err) *err = FR_SBUFF_PARSE_OK; \
1299
6.63k
    *out = cast_num; \
1300
22.5k
  } else { \
1301
22.5k
    if (err) *err = FR_SBUFF_PARSE_OK; \
1302
22.5k
    *out = cast_num; \
1303
22.5k
  } \
1304
29.8k
  return fr_sbuff_advance(in, end - buff); /* Advance by the length strtoull gives us */ \
1305
29.8k
}
1306
1307
/* max chars here is the octal string value with prefix */
1308
4.18k
SBUFF_PARSE_UINT_DEF(uint8, uint8_t, UINT8_MAX, 4, 0)
1309
2.05k
SBUFF_PARSE_UINT_DEF(uint16, uint16_t, UINT16_MAX, 7, 0)
1310
20.8k
SBUFF_PARSE_UINT_DEF(uint32, uint32_t, UINT32_MAX, 12, 0)
1311
5.79k
SBUFF_PARSE_UINT_DEF(uint64, uint64_t, UINT64_MAX, 23, 0)
1312
0
SBUFF_PARSE_UINT_DEF(size, size_t, SIZE_MAX, 23, 0)
1313
1314
0
SBUFF_PARSE_UINT_DEF(uint8_dec, uint8_t, UINT8_MAX, 3, 0)
1315
0
SBUFF_PARSE_UINT_DEF(uint16_dec, uint16_t, UINT16_MAX, 4, 0)
1316
0
SBUFF_PARSE_UINT_DEF(uint32_dec, uint32_t, UINT32_MAX, 10, 0)
1317
0
SBUFF_PARSE_UINT_DEF(uint64_dec, uint64_t, UINT64_MAX, 19, 0)
1318
0
SBUFF_PARSE_UINT_DEF(size_dec, size_t, SIZE_MAX, 19, 0)
1319
1320
1321
0
SBUFF_PARSE_UINT_DEF(uint8_oct, uint8_t, UINT8_MAX, 3, 8)
1322
0
SBUFF_PARSE_UINT_DEF(uint16_oct, uint16_t, UINT16_MAX, 6, 8)
1323
0
SBUFF_PARSE_UINT_DEF(uint32_oct, uint32_t, UINT32_MAX, 11, 8)
1324
0
SBUFF_PARSE_UINT_DEF(uint64_oct, uint64_t, UINT64_MAX, 22, 8)
1325
0
SBUFF_PARSE_UINT_DEF(size_oct, size_t, SIZE_MAX, 22, 8)
1326
1327
0
SBUFF_PARSE_UINT_DEF(uint8_hex, uint8_t, UINT8_MAX, 2, 16)
1328
0
SBUFF_PARSE_UINT_DEF(uint16_hex, uint16_t, UINT16_MAX, 4, 16)
1329
0
SBUFF_PARSE_UINT_DEF(uint32_hex, uint32_t, UINT32_MAX, 8, 16)
1330
0
SBUFF_PARSE_UINT_DEF(uint64_hex, uint64_t, UINT64_MAX, 16, 16)
1331
0
SBUFF_PARSE_UINT_DEF(size_hex, size_t, SIZE_MAX, 22, 16)
1332
1333
/** Used to define a number parsing functions for floats
1334
 *
1335
 * @param[in] _name Function suffix.
1336
 * @param[in] _type Output type.
1337
 * @param[in] _func Parsing function to use.
1338
 * @param[in] _max_char Maximum digits that can be used to represent an integer.
1339
 *      Can't use stringify because of width modifiers like 'u'
1340
 *      used in <stdint.h>.
1341
 */
1342
#define SBUFF_PARSE_FLOAT_DEF(_name, _type, _func, _max_char) \
1343
1.17k
fr_slen_t fr_sbuff_out_##_name(fr_sbuff_parse_error_t *err, _type *out, fr_sbuff_t *in, bool no_trailing) \
1344
1.17k
{ \
1345
1.17k
  char    buff[_max_char + 1] = ""; \
1346
1.17k
  char    *end; \
1347
1.17k
  fr_sbuff_t  our_in = FR_SBUFF(in); \
1348
1.17k
  size_t    len; \
1349
1.17k
  _type   res; \
1350
1.17k
  len = fr_sbuff_out_bstrncpy_allowed(&FR_SBUFF_OUT(buff, sizeof(buff)), &our_in, SIZE_MAX, sbuff_char_class_float); \
1351
1.17k
  if (len == sizeof(buff)) { \
1352
0
    if (err) *err = FR_SBUFF_PARSE_ERROR_NOT_FOUND; \
1353
0
    return -1; \
1354
1.17k
  } else if (len == 0) { \
1355
152
    if (err) *err = (fr_sbuff_remaining(in) == 0) ? FR_SBUFF_PARSE_ERROR_INPUT_EMPTY : FR_SBUFF_PARSE_ERROR_NOT_FOUND; \
1356
152
    return -1; \
1357
152
  } \
1358
1.17k
  errno = 0; /* this is needed as parsing functions don't reset errno */ \
1359
1.01k
  res = _func(buff, &end); \
1360
1.01k
  if (errno == ERANGE) { \
1361
124
    if (res > 0) { \
1362
110
      if (err) *err = FR_SBUFF_PARSE_ERROR_NUM_OVERFLOW; \
1363
110
    } else { \
1364
14
      if (err) *err = FR_SBUFF_PARSE_ERROR_NUM_UNDERFLOW; \
1365
14
    } \
1366
124
    return -1; \
1367
124
  } \
1368
1.01k
  if (no_trailing && (*end != '\0')) { \
1369
194
    if (err) *err = FR_SBUFF_PARSE_ERROR_TRAILING; \
1370
194
    FR_SBUFF_ERROR_RETURN(&our_in); \
1371
194
  } \
1372
894
  *out = res; \
1373
700
  return fr_sbuff_advance(in, end - buff); \
1374
894
}
1375
1376
82
SBUFF_PARSE_FLOAT_DEF(float32, float, strtof, 100)
1377
1.08k
SBUFF_PARSE_FLOAT_DEF(float64, double, strtod, 100)
1378
1379
/** Move data from one sbuff to another
1380
 *
1381
 * @note Do not call this function directly use #fr_sbuff_move
1382
 *
1383
 * Both in and out will be advanced by len, with len set to the shortest
1384
 * value between the user specified value, the number of bytes remaining
1385
 * in the input buffer (after extension), and the number of bytes remaining
1386
 * in the output buffer (after extension).
1387
 *
1388
 * @param[in] out sbuff to copy data to.
1389
 * @param[in] in  sbuff to copy data from.
1390
 * @param[in] len Maximum length of string to copy.
1391
 * @return The amount of data copied.
1392
 */
1393
size_t _fr_sbuff_move_sbuff_to_sbuff(fr_sbuff_t *out, fr_sbuff_t *in, size_t len)
1394
48.9k
{
1395
48.9k
  size_t o_remaining = fr_sbuff_extend_lowat(NULL, out, len);
1396
48.9k
  size_t i_remaining = fr_sbuff_extend_lowat(NULL, in, len);
1397
48.9k
  size_t to_copy = len;
1398
48.9k
  if (to_copy > o_remaining) to_copy = o_remaining;
1399
48.9k
  if (to_copy > i_remaining) to_copy = i_remaining;
1400
48.9k
  safecpy(fr_sbuff_current(out), fr_sbuff_end(out), fr_sbuff_current(in), fr_sbuff_current(in) + to_copy);
1401
48.9k
  return fr_sbuff_advance(out, fr_sbuff_advance(in, to_copy));
1402
48.9k
}
1403
1404
/** Move data from a marker to an sbuff
1405
 *
1406
 * @note Do not call this function directly use #fr_sbuff_move
1407
 *
1408
 * @param[in] out sbuff to copy data to.
1409
 * @param[in] in  marker to copy data from.
1410
 * @param[in] len Maximum length of string to copy.
1411
 * @return The amount of data copied.
1412
 */
1413
size_t _fr_sbuff_move_marker_to_sbuff(fr_sbuff_t *out, fr_sbuff_marker_t *in, size_t len)
1414
0
{
1415
0
  size_t o_remaining = fr_sbuff_extend_lowat(NULL, out, len);
1416
0
  size_t i_remaining = fr_sbuff_extend_lowat(NULL, in, len);
1417
0
  size_t to_copy = len;
1418
0
  if (to_copy > o_remaining) to_copy = o_remaining;
1419
0
  if (to_copy > i_remaining) to_copy = i_remaining;
1420
0
  safecpy(fr_sbuff_current(out), fr_sbuff_end(out), fr_sbuff_current(in), fr_sbuff_current(in) + to_copy);
1421
0
  return fr_sbuff_advance(out, fr_sbuff_advance(in, to_copy));
1422
0
}
1423
1424
/** Move data from one marker to another
1425
 *
1426
 * @note Do not call this function directly use #fr_sbuff_move
1427
 *
1428
 * @param[in] out marker to copy data to.
1429
 * @param[in] in  marker to copy data from.
1430
 * @param[in] len Maximum length of string to copy.
1431
 * @return The amount of data copied.
1432
 */
1433
size_t _fr_sbuff_move_marker_to_marker(fr_sbuff_marker_t *out, fr_sbuff_marker_t *in, size_t len)
1434
0
{
1435
0
  size_t o_remaining = fr_sbuff_extend_lowat(NULL, out, len);
1436
0
  size_t i_remaining = fr_sbuff_extend_lowat(NULL, in, len);
1437
0
  size_t to_copy = len;
1438
0
  if (to_copy > o_remaining) to_copy = o_remaining;
1439
0
  if (to_copy > i_remaining) to_copy = i_remaining;
1440
0
  safecpy(fr_sbuff_current(out), fr_sbuff_end(out), fr_sbuff_current(in), fr_sbuff_current(in) + to_copy);
1441
0
  return fr_sbuff_advance(out, fr_sbuff_advance(in, to_copy));
1442
0
}
1443
1444
/** Move data from an sbuff to a marker
1445
 *
1446
 * @note Do not call this function directly use #fr_sbuff_move
1447
 *
1448
 * @param[in] out marker to copy data to.
1449
 * @param[in] in  sbuff to copy data from.
1450
 * @param[in] len Maximum length of string to copy.
1451
 * @return The amount of data copied.
1452
 */
1453
size_t _fr_sbuff_move_sbuff_to_marker(fr_sbuff_marker_t *out, fr_sbuff_t *in, size_t len)
1454
0
{
1455
0
  size_t o_remaining = fr_sbuff_extend_lowat(NULL, out, len);
1456
0
  size_t i_remaining = fr_sbuff_extend_lowat(NULL, in, len);
1457
0
  size_t to_copy = len;
1458
0
  if (to_copy > o_remaining) to_copy = o_remaining;
1459
0
  if (to_copy > i_remaining) to_copy = i_remaining;
1460
0
  safecpy(fr_sbuff_current(out), fr_sbuff_end(out), fr_sbuff_current(in), fr_sbuff_current(in) + to_copy);
1461
0
  return fr_sbuff_advance(out, fr_sbuff_advance(in, to_copy));
1462
0
}
1463
1464
/** Copy bytes into the sbuff up to the first \0
1465
 *
1466
 * @param[in] sbuff to copy into.
1467
 * @param[in] str to copy into buffer.
1468
 * @return
1469
 *  - >= 0 the number of bytes copied into the sbuff.
1470
 *  - <0 the number of bytes required to complete the copy operation.
1471
 */
1472
ssize_t fr_sbuff_in_strcpy(fr_sbuff_t *sbuff, char const *str)
1473
547
{
1474
547
  size_t len;
1475
1476
547
  CHECK_SBUFF_WRITEABLE(sbuff);
1477
1478
547
  len = strlen(str);
1479
547
  FR_SBUFF_EXTEND_LOWAT_OR_RETURN(sbuff, len);
1480
1481
547
  safecpy(sbuff->p, sbuff->end, str, str + len);
1482
547
  sbuff->p[len] = '\0';
1483
1484
547
  return fr_sbuff_advance(sbuff, len);
1485
547
}
1486
1487
/** Copy bytes into the sbuff up to the first \0
1488
 *
1489
 * @param[in] sbuff to copy into.
1490
 * @param[in] str to copy into buffer.
1491
 * @param[in] len number of bytes to copy.
1492
 * @return
1493
 *  - >= 0 the number of bytes copied into the sbuff.
1494
 *  - <0 the number of bytes required to complete the copy operation.
1495
 */
1496
ssize_t fr_sbuff_in_bstrncpy(fr_sbuff_t *sbuff, char const *str, size_t len)
1497
25.8M
{
1498
25.8M
  CHECK_SBUFF_WRITEABLE(sbuff);
1499
1500
25.8M
  FR_SBUFF_EXTEND_LOWAT_OR_RETURN(sbuff, len);
1501
1502
25.8M
  safecpy(sbuff->p, sbuff->end, str, str + len);
1503
25.8M
  sbuff->p[len] = '\0';
1504
1505
25.8M
  return fr_sbuff_advance(sbuff, len);
1506
25.8M
}
1507
1508
/** Copy bytes into the sbuff up to the first \0
1509
 *
1510
 * @param[in] sbuff to copy into.
1511
 * @param[in] str talloced buffer to copy into sbuff.
1512
 * @return
1513
 *  - >= 0 the number of bytes copied into the sbuff.
1514
 *  - <0 the number of bytes required to complete the copy operation.
1515
 */
1516
ssize_t fr_sbuff_in_bstrcpy_buffer(fr_sbuff_t *sbuff, char const *str)
1517
18.7M
{
1518
18.7M
  size_t len;
1519
1520
18.7M
  CHECK_SBUFF_WRITEABLE(sbuff);
1521
1522
18.7M
  len = talloc_strlen(str);
1523
1524
18.7M
  FR_SBUFF_EXTEND_LOWAT_OR_RETURN(sbuff, len);
1525
1526
18.7M
  safecpy(sbuff->p, sbuff->end, str, str + len);
1527
18.7M
  sbuff->p[len] = '\0';
1528
1529
18.7M
  return fr_sbuff_advance(sbuff, len);
1530
18.7M
}
1531
1532
/** Free the scratch buffer used for printf
1533
 *
1534
 */
1535
static int _sbuff_scratch_free(void *arg)
1536
20
{
1537
20
  sbuff_scratch_freed = true;
1538
20
  return talloc_free(arg);
1539
20
}
1540
1541
static inline CC_HINT(always_inline) int sbuff_scratch_init(TALLOC_CTX **out)
1542
18.7M
{
1543
18.7M
  TALLOC_CTX  *scratch;
1544
1545
18.7M
  if (sbuff_scratch_freed) {
1546
0
    *out = NULL;
1547
0
    return 0;
1548
0
  }
1549
1550
18.7M
  scratch = sbuff_scratch;
1551
18.7M
  if (!scratch) {
1552
20
    scratch = talloc_pool(NULL, 4096);
1553
20
    if (unlikely(!scratch)) {
1554
0
      fr_strerror_const("Out of Memory");
1555
0
      return -1;
1556
0
    }
1557
20
    fr_atexit_thread_local(sbuff_scratch, _sbuff_scratch_free, scratch);
1558
20
  }
1559
1560
18.7M
  *out = scratch;
1561
1562
18.7M
  return 0;
1563
18.7M
}
1564
1565
/** Print using a fmt string to an sbuff
1566
 *
1567
 * @param[in] sbuff to print into.
1568
 * @param[in] fmt string.
1569
 * @param[in] ap  arguments for format string.
1570
< * @return
1571
 *  - >= 0 the number of bytes printed into the sbuff.
1572
 *  - <0 the number of bytes required to complete the print operation.
1573
 */
1574
ssize_t fr_sbuff_in_vsprintf(fr_sbuff_t *sbuff, char const *fmt, va_list ap)
1575
18.7M
{
1576
18.7M
  TALLOC_CTX  *scratch;
1577
18.7M
  va_list   ap_p;
1578
18.7M
  char    *tmp;
1579
18.7M
  ssize_t   slen;
1580
1581
18.7M
  CHECK_SBUFF_WRITEABLE(sbuff);
1582
1583
18.7M
  if (sbuff_scratch_init(&scratch) < 0) return 0;
1584
1585
18.7M
  va_copy(ap_p, ap);
1586
18.7M
  tmp = fr_vasprintf(scratch, fmt, ap_p);
1587
18.7M
  va_end(ap_p);
1588
18.7M
  if (!tmp) return 0;
1589
1590
18.7M
  slen = fr_sbuff_in_bstrcpy_buffer(sbuff, tmp);
1591
18.7M
  talloc_free(tmp); /* Free the temporary buffer */
1592
1593
18.7M
  return slen;
1594
18.7M
}
1595
1596
/** Print using a fmt string to an sbuff
1597
 *
1598
 * @param[in] sbuff to print into.
1599
 * @param[in] fmt string.
1600
 * @param[in] ... arguments for format string.
1601
 * @return
1602
 *  - >= 0 the number of bytes printed into the sbuff.
1603
 *  - <0 the number of bytes required to complete the print operation.
1604
 */
1605
ssize_t fr_sbuff_in_sprintf(fr_sbuff_t *sbuff, char const *fmt, ...)
1606
18.7M
{
1607
18.7M
  va_list   ap;
1608
18.7M
  ssize_t   slen;
1609
1610
18.7M
  CHECK_SBUFF_WRITEABLE(sbuff);
1611
1612
18.7M
  va_start(ap, fmt);
1613
18.7M
  slen = fr_sbuff_in_vsprintf(sbuff, fmt, ap);
1614
18.7M
  va_end(ap);
1615
1616
18.7M
  return slen;
1617
18.7M
}
1618
1619
/** Print an escaped string to an sbuff
1620
 *
1621
 * @param[in] sbuff to print into.
1622
 * @param[in] in  to escape.
1623
 * @param[in] inlen of string to escape.
1624
 * @param[in] e_rules Escaping rules.  Used to escape special characters
1625
 *          as data is written to the sbuff.  May be NULL.
1626
 * @return
1627
 *  - >= 0 the number of bytes printed into the sbuff.
1628
 *  - <0 the number of bytes required to complete the print operation.
1629
 */
1630
ssize_t fr_sbuff_in_escape(fr_sbuff_t *sbuff, char const *in, size_t inlen, fr_sbuff_escape_rules_t const *e_rules)
1631
7.37k
{
1632
7.37k
  char const  *end = in + inlen;
1633
7.37k
  char const  *p = in;
1634
7.37k
  fr_sbuff_t  our_sbuff;
1635
1636
  /* Significantly quicker if there are no rules */
1637
7.37k
  if (!e_rules || (e_rules->chr == '\0')) return fr_sbuff_in_bstrncpy(sbuff, in, inlen);
1638
1639
7.37k
  CHECK_SBUFF_WRITEABLE(sbuff);
1640
1641
7.37k
  our_sbuff = FR_SBUFF(sbuff);
1642
42.5M
  while (p < end) {
1643
42.5M
    size_t  clen;
1644
42.5M
    uint8_t c = (uint8_t)*p;
1645
42.5M
    char  sub;
1646
1647
    /*
1648
     *  We don't support escaping UTF8 sequences
1649
     *  as they're not used anywhere in our
1650
     *  grammar.
1651
     */
1652
42.5M
    if (e_rules->do_utf8 && ((clen = fr_utf8_char((uint8_t const *)p, end - p)) > 1)) {
1653
182k
      FR_SBUFF_IN_BSTRNCPY_RETURN(&our_sbuff, p, clen);
1654
182k
      p += clen;
1655
182k
      continue;
1656
182k
    }
1657
1658
    /*
1659
     *  Check if there's a special substitution
1660
     *  like 0x0a -> \n.
1661
     */
1662
42.4M
    sub = e_rules->subs[c];
1663
42.4M
    if (sub != '\0') {
1664
320k
      FR_SBUFF_IN_CHAR_RETURN(&our_sbuff, e_rules->chr, sub);
1665
320k
      p++;
1666
320k
      continue;
1667
320k
    }
1668
1669
    /*
1670
     *  Check if the character is in the range
1671
     *  we escape.
1672
     */
1673
42.0M
    if (e_rules->esc[c]) {
1674
      /*
1675
       *  For legacy reasons we prefer
1676
       *  octal escape sequences.
1677
       */
1678
16.7M
      if (e_rules->do_oct) {
1679
16.7M
        FR_SBUFF_IN_SPRINTF_RETURN(&our_sbuff, "%c%03o", e_rules->chr, (uint8_t)*p++);
1680
16.7M
        continue;
1681
16.7M
      } else if (e_rules->do_hex) {
1682
0
        FR_SBUFF_IN_SPRINTF_RETURN(&our_sbuff, "%cx%02x", e_rules->chr, (uint8_t)*p++);
1683
0
        continue;
1684
0
      }
1685
16.7M
    }
1686
1687
25.3M
    FR_SBUFF_IN_CHAR_RETURN(&our_sbuff, *p++);
1688
25.3M
  }
1689
1690
7.37k
  FR_SBUFF_SET_RETURN(sbuff, &our_sbuff);
1691
7.37k
}
1692
1693
/** Print an escaped string to an sbuff taking a talloced buffer as input
1694
 *
1695
 * @param[in] sbuff to print into.
1696
 * @param[in] in  to escape.
1697
 * @param[in] e_rules Escaping rules.  Used to escape special characters
1698
 *          as data is written to the sbuff.  May be NULL.
1699
 * @return
1700
 *  - >= 0 the number of bytes printed into the sbuff.
1701
 *  - <0 the number of bytes required to complete the print operation.
1702
 */
1703
ssize_t fr_sbuff_in_escape_buffer(fr_sbuff_t *sbuff, char const *in, fr_sbuff_escape_rules_t const *e_rules)
1704
0
{
1705
0
  if (unlikely(!in)) return 0;
1706
1707
0
  CHECK_SBUFF_WRITEABLE(sbuff);
1708
1709
0
  return fr_sbuff_in_escape(sbuff, in, talloc_strlen(in), e_rules);
1710
0
}
1711
1712
/** Concat an array of strings (NULL terminated), with a string separator
1713
 *
1714
 * @param[out] out  Where to write the resulting string.
1715
 * @param[in] array of strings to concat.
1716
 * @param[in] sep to insert between elements.  May be NULL.
1717
 * @return
1718
 *      - >= 0 on success - length of the string created.
1719
 *  - <0 on failure.  How many bytes we would need.
1720
 */
1721
fr_slen_t fr_sbuff_in_array(fr_sbuff_t *out, char const * const *array, char const *sep)
1722
0
{
1723
0
  fr_sbuff_t    our_out = FR_SBUFF(out);
1724
0
  char const * const *  p;
1725
0
  fr_sbuff_escape_rules_t e_rules = {
1726
0
          .name = __FUNCTION__,
1727
0
          .chr = '\\'
1728
0
        };
1729
1730
0
  if (sep) e_rules.subs[(uint8_t)*sep] = *sep;
1731
1732
0
  CHECK_SBUFF_WRITEABLE(out);
1733
1734
0
  for (p = array; *p; p++) {
1735
0
    if (*p) FR_SBUFF_RETURN(fr_sbuff_in_escape, &our_out, *p, strlen(*p), &e_rules);
1736
1737
0
    if (sep && p[1]) {
1738
0
      FR_SBUFF_RETURN(fr_sbuff_in_strcpy, &our_out, sep);
1739
0
    }
1740
0
  }
1741
1742
0
  FR_SBUFF_SET_RETURN(out, &our_out);
1743
0
}
1744
1745
/** Return true and advance past the end of the needle if needle occurs next in the sbuff
1746
 *
1747
 * @param[in] sbuff   to search in.
1748
 * @param[in] needle    to search for.
1749
 * @param[in] needle_len  of needle. If SIZE_MAX strlen is used
1750
 *        to determine length of the needle.
1751
 * @return how many bytes we advanced
1752
 */
1753
size_t fr_sbuff_adv_past_str(fr_sbuff_t *sbuff, char const *needle, size_t needle_len)
1754
2.18k
{
1755
2.18k
  char const *found;
1756
1757
2.18k
  CHECK_SBUFF_INIT(sbuff);
1758
1759
2.18k
  if (needle_len == SIZE_MAX) needle_len = strlen(needle);
1760
1761
  /*
1762
   *  If there's insufficient bytes in the
1763
   *  buffer currently, try to extend it,
1764
   *  returning if we can't.
1765
   */
1766
2.18k
  if (fr_sbuff_extend_lowat(NULL, sbuff, needle_len) < needle_len) return 0;
1767
1768
2.08k
  found = memmem(sbuff->p, needle_len, needle, needle_len); /* sbuff needle_len and needle needle_len ensures match must be next */
1769
2.08k
  if (!found) return 0;
1770
1771
101
  return fr_sbuff_advance(sbuff, needle_len);
1772
2.08k
}
1773
1774
/** Return true and advance past the end of the needle if needle occurs next in the sbuff
1775
 *
1776
 * This function is similar to fr_sbuff_adv_past_str but is case insensitive.
1777
 *
1778
 * @param[in] sbuff   to search in.
1779
 * @param[in] needle    to search for.
1780
 * @param[in] needle_len  of needle. If SIZE_MAX strlen is used
1781
 *        to determine length of the needle.
1782
 * @return how many bytes we advanced
1783
 */
1784
size_t fr_sbuff_adv_past_strcase(fr_sbuff_t *sbuff, char const *needle, size_t needle_len)
1785
196
{
1786
196
  char const *p, *n_p;
1787
196
  char const *end;
1788
1789
196
  CHECK_SBUFF_INIT(sbuff);
1790
1791
196
  if (needle_len == SIZE_MAX) needle_len = strlen(needle);
1792
1793
  /*
1794
   *  If there's insufficient bytes in the
1795
   *  buffer currently, try to extend it,
1796
   *  returning if we can't.
1797
   */
1798
196
  if (fr_sbuff_extend_lowat(NULL, sbuff, needle_len) < needle_len) return 0;
1799
1800
190
  p = sbuff->p;
1801
190
  end = p + needle_len;
1802
1803
410
  for (p = sbuff->p, n_p = needle; p < end; p++, n_p++) {
1804
320
    if (tolower((uint8_t) *p) != tolower((uint8_t) *n_p)) return 0;
1805
320
  }
1806
1807
90
  return fr_sbuff_advance(sbuff, needle_len);
1808
190
}
1809
1810
/** Wind position past characters in the allowed set
1811
 *
1812
 * @param[in] sbuff   sbuff to search in.
1813
 * @param[in] len   Maximum amount to advance by. Unconstrained if SIZE_MAX.
1814
 * @param[in] allowed   character set.
1815
 * @param[in] tt    If not NULL, stop if we find a terminal sequence.
1816
 * @return how many bytes we advanced.
1817
 */
1818
size_t fr_sbuff_adv_past_allowed(fr_sbuff_t *sbuff, size_t len, bool
1819
         const allowed[static SBUFF_CHAR_CLASS], fr_sbuff_term_t const *tt)
1820
53.6k
{
1821
53.6k
  size_t    total = 0;
1822
53.6k
  char const  *p;
1823
53.6k
  uint8_t   idx[SBUFF_CHAR_CLASS];  /* Fast path index */
1824
53.6k
  size_t    needle_len = 0;
1825
1826
53.6k
  CHECK_SBUFF_INIT(sbuff);
1827
1828
53.6k
  if (tt) fr_sbuff_terminal_idx_init(&needle_len, idx, tt);
1829
1830
55.8k
  while (total < len) {
1831
53.7k
    char *end;
1832
1833
53.7k
    if (!fr_sbuff_extend(sbuff)) break;
1834
1835
53.6k
    end = CONSTRAINED_END(sbuff, len, total);
1836
53.6k
    p = sbuff->p;
1837
24.9M
    while ((p < end) && allowed[(uint8_t)*p]) {
1838
24.9M
      if (needle_len == 0) {
1839
24.9M
        p++;
1840
24.9M
        continue;
1841
24.9M
      }
1842
1843
           /*
1844
      * If this character is allowed, BUT is also listed as a one-character terminal,
1845
      * then we still allow it.  This decision implements "greedy" parsing.
1846
      */
1847
0
           if (fr_sbuff_terminal_search(sbuff, p, idx, tt, 1)) {
1848
0
             p++;
1849
0
             continue;
1850
0
           }
1851
1852
           /*
1853
      * Otherwise if the next *set* of characters) is not in the terminals, then
1854
      * allow the current character.
1855
      */
1856
0
           if (!fr_sbuff_terminal_search(sbuff, p, idx, tt, needle_len)) {
1857
0
             p++;
1858
0
             continue;
1859
0
           }
1860
1861
           /*
1862
      * The character is allowed, and is NOT listed as a terminal character by itself.
1863
      * However, it is part of a multi-character terminal sequence.  We therefore
1864
      * stop.
1865
      *
1866
      * This decision allows us to parse things like "Framed-User", where we might
1867
      * normally stop at the "-".  However, we will still stop at "Framed-=User", as
1868
      * "-=" may be a terminal sequence.
1869
      *
1870
      * There is no perfect solution here, other than to fix the input grammar so that
1871
      * it has no ambiguity.  Since we can't do that, we choose to err on the side of
1872
      * allowing the existing grammar, where it makes sense
1873
      */
1874
0
           break;
1875
0
    }
1876
1877
53.6k
    total += fr_sbuff_set(sbuff, p);
1878
53.6k
    if (p != end) break;   /* stopped early, break */
1879
53.6k
  }
1880
1881
53.6k
  return total;
1882
53.6k
}
1883
1884
/** Wind position until we hit a character in the terminal set
1885
 *
1886
 * @param[in] sbuff   sbuff to search in.
1887
 * @param[in] len   Maximum amount to advance by. Unconstrained if SIZE_MAX.
1888
 * @param[in] tt    Token terminals in the encompassing grammar.
1889
 * @param[in] escape_chr  If not '\0', ignore characters in the tt set when
1890
 *        prefixed with this escape character.
1891
 * @return how many bytes we advanced.
1892
 */
1893
size_t fr_sbuff_adv_until(fr_sbuff_t *sbuff, size_t len, fr_sbuff_term_t const *tt, char escape_chr)
1894
747
{
1895
747
  size_t    total = 0;
1896
747
  char const  *p;
1897
747
  bool    do_escape = false;    /* Track state across extensions */
1898
1899
747
  uint8_t   idx[SBUFF_CHAR_CLASS];    /* Fast path index */
1900
747
  size_t    needle_len = 1;
1901
1902
747
  CHECK_SBUFF_INIT(sbuff);
1903
1904
  /*
1905
   *  Initialise the fastpath index and
1906
   *  figure out the longest needle.
1907
   */
1908
747
  fr_sbuff_terminal_idx_init(&needle_len, idx, tt);
1909
1910
1.49k
  while (total < len) {
1911
1.49k
    char *end;
1912
1913
1.49k
    if (fr_sbuff_extend_lowat(NULL, sbuff, needle_len) == 0) break;
1914
1915
747
    end = CONSTRAINED_END(sbuff, len, total);
1916
747
    p = sbuff->p;
1917
1918
747
    if (escape_chr == '\0') {
1919
34.6k
      while ((p < end) && !fr_sbuff_terminal_search(sbuff, p, idx, tt, needle_len)) p++;
1920
747
    } else {
1921
0
      while (p < end) {
1922
0
        if (do_escape) {
1923
0
          do_escape = false;
1924
0
        } else if (*p == escape_chr) {
1925
0
          do_escape = true;
1926
0
        } else if (fr_sbuff_terminal_search(sbuff, p, idx, tt, needle_len)) {
1927
0
          break;
1928
0
        }
1929
0
        p++;
1930
0
      }
1931
0
    }
1932
1933
747
    total += fr_sbuff_set(sbuff, p);
1934
747
    if (p != end) break; /* stopped early, break */
1935
747
  }
1936
1937
747
  return total;
1938
747
}
1939
1940
/** Wind position to first instance of specified multibyte utf8 char
1941
 *
1942
 * Only use this function if the search char could be multibyte,
1943
 * as there's a large performance penalty.
1944
 *
1945
 * @param[in,out] sbuff   to search in.
1946
 * @param[in] len   the maximum number of characters to search in sbuff.
1947
 * @param[in] chr   to search for.
1948
 * @return
1949
 *  - NULL, no instances found.
1950
 *  - The position of the first character.
1951
 */
1952
char *fr_sbuff_adv_to_chr_utf8(fr_sbuff_t *sbuff, size_t len, char const *chr)
1953
0
{
1954
0
  fr_sbuff_t  our_sbuff = FR_SBUFF(sbuff);
1955
0
  size_t    total = 0;
1956
0
  size_t    clen = strlen(chr);
1957
1958
0
  CHECK_SBUFF_INIT(sbuff);
1959
1960
  /*
1961
   *  Needle bigger than haystack
1962
   */
1963
0
  if (len < clen) return NULL;
1964
1965
0
  while (total <= (len - clen)) {
1966
0
    char const  *found;
1967
0
    char    *end;
1968
1969
    /*
1970
     *  Ensure we have enough chars to match
1971
     *  the needle.
1972
     */
1973
0
    if (fr_sbuff_extend_lowat(NULL, &our_sbuff, clen) < clen) break;
1974
1975
0
    end = CONSTRAINED_END(&our_sbuff, len, total);
1976
1977
0
    found = fr_utf8_strchr(NULL, our_sbuff.p, end - our_sbuff.p, chr);
1978
0
    if (found) {
1979
0
      (void)fr_sbuff_set(sbuff, found);
1980
0
      return sbuff->p;
1981
0
    }
1982
0
    total += fr_sbuff_set(&our_sbuff, (end - clen) + 1);
1983
0
  }
1984
1985
0
  return NULL;
1986
0
}
1987
1988
/** Wind position to first instance of specified char
1989
 *
1990
 * @param[in,out] sbuff   to search in.
1991
 * @param[in] len   Maximum amount to advance by. Unconstrained if SIZE_MAX.
1992
 * @param[in] c     to search for.
1993
 * @return
1994
 *  - NULL, no instances found.
1995
 *  - The position of the first character.
1996
 */
1997
char *fr_sbuff_adv_to_chr(fr_sbuff_t *sbuff, size_t len, char c)
1998
0
{
1999
0
  fr_sbuff_t  our_sbuff = FR_SBUFF(sbuff);
2000
0
  size_t    total = 0;
2001
2002
0
  CHECK_SBUFF_INIT(sbuff);
2003
2004
0
  while (total < len) {
2005
0
    char const  *found;
2006
0
    char    *end;
2007
2008
0
    if (!fr_sbuff_extend(&our_sbuff)) break;
2009
2010
0
    end = CONSTRAINED_END(&our_sbuff, len, total);
2011
0
    found = memchr(our_sbuff.p, c, end - our_sbuff.p);
2012
0
    if (found) {
2013
0
      (void)fr_sbuff_set(sbuff, found);
2014
0
      return sbuff->p;
2015
0
    }
2016
2017
0
    total += fr_sbuff_set(&our_sbuff, end);
2018
0
  }
2019
2020
0
  return NULL;
2021
0
}
2022
2023
/** Wind position to the first instance of the specified needle
2024
 *
2025
 * @param[in,out] sbuff   sbuff to search in.
2026
 * @param[in] len   Maximum amount to advance by. Unconstrained if SIZE_MAX.
2027
 * @param[in] needle    to search for.
2028
 * @param[in] needle_len  Length of the needle. SIZE_MAX to used strlen.
2029
 * @return
2030
 *  - NULL, no instances found.
2031
 *  - The position of the first character.
2032
 */
2033
char *fr_sbuff_adv_to_str(fr_sbuff_t *sbuff, size_t len, char const *needle, size_t needle_len)
2034
0
{
2035
0
  fr_sbuff_t  our_sbuff = FR_SBUFF(sbuff);
2036
0
  size_t    total = 0;
2037
2038
0
  CHECK_SBUFF_INIT(sbuff);
2039
2040
0
  if (needle_len == SIZE_MAX) needle_len = strlen(needle);
2041
0
  if (!needle_len) return NULL;
2042
2043
  /*
2044
   *  Needle bigger than haystack
2045
   */
2046
0
  if (len < needle_len) return NULL;
2047
2048
0
  while (total <= (len - needle_len)) {
2049
0
    char const  *found;
2050
0
    char    *end;
2051
2052
    /*
2053
     *  If the needle is longer than
2054
     *  the remaining buffer, return.
2055
     */
2056
0
    if (fr_sbuff_extend_lowat(NULL, &our_sbuff, needle_len) < needle_len) break;
2057
2058
0
    end = CONSTRAINED_END(&our_sbuff, len, total);
2059
0
    found = memmem(our_sbuff.p, end - our_sbuff.p, needle, needle_len);
2060
0
    if (found) {
2061
0
      (void)fr_sbuff_set(sbuff, found);
2062
0
      return sbuff->p;
2063
0
    }
2064
2065
    /*
2066
     *  Partial needle may be in
2067
     *      the end of the buffer so
2068
     *  don't advance too far.
2069
     */
2070
0
    total += fr_sbuff_set(&our_sbuff, (end - needle_len) + 1);
2071
0
  }
2072
2073
0
  return NULL;
2074
0
}
2075
2076
/** Wind position to the first instance of the specified needle
2077
 *
2078
 * @param[in,out] sbuff   sbuff to search in.
2079
 * @param[in] len   Maximum amount to advance by. Unconstrained if SIZE_MAX.
2080
 * @param[in] needle    to search for.
2081
 * @param[in] needle_len  Length of the needle. SIZE_MAX to used strlen.
2082
 * @return
2083
 *  - NULL, no instances found.
2084
 *  - The position of the first character.
2085
 */
2086
char *fr_sbuff_adv_to_strcase(fr_sbuff_t *sbuff, size_t len, char const *needle, size_t needle_len)
2087
0
{
2088
0
  fr_sbuff_t  our_sbuff = FR_SBUFF(sbuff);
2089
0
  size_t    total = 0;
2090
2091
0
  CHECK_SBUFF_INIT(sbuff);
2092
2093
0
  if (needle_len == SIZE_MAX) needle_len = strlen(needle);
2094
0
  if (!needle_len) return NULL;
2095
2096
  /*
2097
   *  Needle bigger than haystack
2098
   */
2099
0
  if (len < needle_len) return NULL;
2100
2101
0
  while (total <= (len - needle_len)) {
2102
0
    char *p, *end;
2103
0
    char const *n_p;
2104
2105
0
    if (fr_sbuff_extend_lowat(NULL, &our_sbuff, needle_len) < needle_len) break;
2106
2107
0
    for (p = our_sbuff.p, n_p = needle, end = our_sbuff.p + needle_len;
2108
0
         (p < end) && (tolower((uint8_t) *p) == tolower((uint8_t) *n_p));
2109
0
         p++, n_p++);
2110
0
    if (p == end) {
2111
0
      (void)fr_sbuff_set(sbuff, our_sbuff.p);
2112
0
      return sbuff->p;
2113
0
    }
2114
2115
0
    total += fr_sbuff_advance(&our_sbuff, 1);
2116
0
  }
2117
2118
0
  return NULL;
2119
0
}
2120
2121
/** Return true if the current char matches, and if it does, advance
2122
 *
2123
 * @param[in] sbuff to search for char in.
2124
 * @param[in] c   char to search for.
2125
 * @return
2126
 *  - true and advance if the next character matches.
2127
 *  - false and don't advance if the next character doesn't match.
2128
 */
2129
bool fr_sbuff_next_if_char(fr_sbuff_t *sbuff, char c)
2130
24.6k
{
2131
24.6k
  CHECK_SBUFF_INIT(sbuff);
2132
2133
24.6k
  if (!fr_sbuff_extend(sbuff)) return false;
2134
2135
16.8k
  if (*sbuff->p != c) return false;
2136
2137
6.28k
  fr_sbuff_advance(sbuff, 1);
2138
2139
6.28k
  return true;
2140
16.8k
}
2141
2142
/** Return true and advance if the next char does not match
2143
 *
2144
 * @param[in] sbuff to search for char in.
2145
 * @param[in] c   char to search for.
2146
 * @return
2147
 *  - true and advance unless the character matches.
2148
 *  - false and don't advance if the next character matches.
2149
 */
2150
bool fr_sbuff_next_unless_char(fr_sbuff_t *sbuff, char c)
2151
0
{
2152
0
  CHECK_SBUFF_INIT(sbuff);
2153
2154
0
  if (!fr_sbuff_extend(sbuff)) return false;
2155
2156
0
  if (*sbuff->p == c) return false;
2157
2158
0
  fr_sbuff_advance(sbuff, 1);
2159
2160
0
  return true;
2161
0
}
2162
2163
/** Trim trailing characters from a string we're composing
2164
 *
2165
 * @param[in] sbuff   to trim trailing characters from.
2166
 * @param[in] to_trim   Charset to trim.
2167
 * @return how many chars we removed.
2168
 */
2169
size_t fr_sbuff_trim(fr_sbuff_t *sbuff, bool const to_trim[static SBUFF_CHAR_CLASS])
2170
0
{
2171
0
  char  *p = sbuff->p - 1;
2172
0
  ssize_t slen;
2173
2174
0
  while ((p >= sbuff->start) && to_trim[(uint8_t)*p]) p--;
2175
2176
0
  slen = fr_sbuff_set(sbuff, p + 1);
2177
0
  if (slen != 0) fr_sbuff_terminate(sbuff);
2178
2179
0
  return slen;
2180
0
}
2181
2182
/** Efficient terminal string search
2183
 *
2184
 * Caller should ensure that a buffer extension of needle_len bytes has been requested
2185
 * before calling this function.
2186
 *
2187
 * @param[in] in  Sbuff to search in.
2188
 * @param[in] tt  Token terminals in the encompassing grammar.
2189
 * @return
2190
 *      - true if found.
2191
 *  - false if not.
2192
 */
2193
bool fr_sbuff_is_terminal(fr_sbuff_t *in, fr_sbuff_term_t const *tt)
2194
2.71k
{
2195
2.71k
  uint8_t   idx[SBUFF_CHAR_CLASS];  /* Fast path index */
2196
2.71k
  size_t    needle_len = 1;
2197
2198
  /*
2199
   *  No terminal, check for EOF.
2200
   */
2201
2.71k
  if (!tt) {
2202
2.71k
    fr_sbuff_extend_status_t status = 0;
2203
2204
2.71k
    if ((fr_sbuff_extend_lowat(&status, in, 1) == 0) &&
2205
735
        (status & FR_SBUFF_FLAG_EXTEND_ERROR) == 0) {
2206
735
      return true;
2207
735
    }
2208
2209
1.97k
    return false;
2210
2.71k
  }
2211
2212
  /*
2213
   *  Initialise the fastpath index and
2214
   *  figure out the longest needle.
2215
   */
2216
0
  fr_sbuff_terminal_idx_init(&needle_len, idx, tt);
2217
2218
0
  fr_sbuff_extend_lowat(NULL, in, needle_len);
2219
2220
0
  return fr_sbuff_terminal_search(in, in->p, idx, tt, needle_len);
2221
2.71k
}
2222
2223
/** Print a char in a friendly format
2224
 *
2225
 */
2226
static char const *sbuff_print_char(char c)
2227
0
{
2228
0
  static bool const unprintables[SBUFF_CHAR_CLASS] = {
2229
0
    SBUFF_CHAR_UNPRINTABLES_LOW,
2230
0
    SBUFF_CHAR_UNPRINTABLES_EXTENDED
2231
0
  };
2232
2233
0
  static _Thread_local char str[10][5];
2234
0
  static _Thread_local size_t i = 0;
2235
2236
0
  switch (c) {
2237
0
  case '\a':
2238
0
    return "\a";
2239
2240
0
  case '\b':
2241
0
    return "\b";
2242
2243
0
  case '\n':
2244
0
    return "\n";
2245
2246
0
  case '\r':
2247
0
    return "\r";
2248
2249
0
  case '\t':
2250
0
    return "\t";
2251
2252
0
  case '\f':
2253
0
    return "\f";
2254
2255
0
  case '\v':
2256
0
    return "\v";
2257
2258
0
  default:
2259
0
    if (i >= NUM_ELEMENTS(str)) i = 0;
2260
2261
0
    if (unprintables[(uint8_t)c]) {
2262
0
      snprintf(str[i], sizeof(str[i]), "\\x%02x", (uint8_t) c);
2263
0
      return str[i++];
2264
0
    }
2265
2266
0
    str[i][0] = c;
2267
0
    str[i][1] = '\0';
2268
0
    return str[i++];
2269
0
  }
2270
0
}
2271
2272
void fr_sbuff_unescape_debug(FILE *fp, fr_sbuff_unescape_rules_t const *escapes)
2273
0
{
2274
0
  int i;
2275
2276
0
  fprintf(fp, "Escape rules %s (%p)\n", escapes->name, escapes);
2277
0
  fprintf(fp, "chr     : %c\n", escapes->chr ? escapes->chr : ' ');
2278
0
  fprintf(fp, "do_hex  : %s\n", escapes->do_hex ? "yes" : "no");
2279
0
  fprintf(fp, "do_oct  : %s\n", escapes->do_oct ? "yes" : "no");
2280
2281
0
  fprintf(fp, "substitutions:\n");
2282
0
  for (i = 0; i < SBUFF_CHAR_CLASS; i++) {
2283
0
    if (escapes->subs[i]) FR_FAULT_LOG("\t%s -> %s\n",
2284
0
               sbuff_print_char((char)i),
2285
0
               sbuff_print_char((char)escapes->subs[i]));
2286
0
  }
2287
0
  fprintf(fp, "skips:\n");
2288
0
  for (i = 0; i < SBUFF_CHAR_CLASS; i++) {
2289
0
    if (escapes->skip[i]) fprintf(fp, "\t%s\n", sbuff_print_char((char)i));
2290
0
  }
2291
0
}
2292
2293
void fr_sbuff_terminal_debug(FILE *fp, fr_sbuff_term_t const *tt)
2294
0
{
2295
0
  size_t i;
2296
2297
0
  fprintf(fp, "Terminal count %zu\n", tt->len);
2298
2299
0
  for (i = 0; i < tt->len; i++) fprintf(fp, "\t\"%s\" (%zu)\n", tt->elem[i].str, tt->elem[i].len);
2300
0
}
2301
2302
void fr_sbuff_parse_rules_debug(FILE *fp, fr_sbuff_parse_rules_t const *p_rules)
2303
0
{
2304
0
  fprintf(fp, "Parse rules %p\n", p_rules);
2305
2306
0
  FR_FAULT_LOG("Escapes - ");
2307
0
  if (p_rules->escapes) {
2308
0
    fr_sbuff_unescape_debug(fp, p_rules->escapes);
2309
0
  } else {
2310
0
    fprintf(fp, "<none>\n");
2311
0
  }
2312
2313
0
  FR_FAULT_LOG("Terminals - ");
2314
0
  if (p_rules->terminals) {
2315
0
    fr_sbuff_terminal_debug(fp, p_rules->terminals);
2316
0
  } else {
2317
0
    fprintf(fp, "<none>\n");
2318
0
  }
2319
0
}
2320
2321
/** Concat an array of strings (not NULL terminated), with a string separator
2322
 *
2323
 * @param[out] out  Where to write the resulting string.
2324
 * @param[in] array of strings to concat.
2325
 * @param[in] sep to insert between elements.  May be NULL.
2326
 * @return
2327
 *      - >= 0 on success - length of the string created.
2328
 *  - <0 on failure.  How many bytes we would need.
2329
 */
2330
fr_slen_t fr_sbuff_array_concat(fr_sbuff_t *out, char const * const *array, char const *sep)
2331
0
{
2332
0
  fr_sbuff_t    our_out = FR_SBUFF(out);
2333
0
  size_t      len = talloc_array_length(array);
2334
0
  char const * const *  p;
2335
0
  char const * const *  end;
2336
0
  fr_sbuff_escape_rules_t e_rules = {
2337
0
          .name = __FUNCTION__,
2338
0
          .chr = '\\'
2339
0
        };
2340
2341
0
  if (sep) e_rules.subs[(uint8_t)*sep] = *sep;
2342
2343
0
  for (p = array, end = array + len;
2344
0
       (p < end);
2345
0
       p++) {
2346
0
    if (*p) FR_SBUFF_RETURN(fr_sbuff_in_escape, &our_out, *p, strlen(*p), &e_rules);
2347
2348
0
    if (sep && ((p + 1) < end)) {
2349
0
      FR_SBUFF_RETURN(fr_sbuff_in_strcpy, &our_out, sep);
2350
0
    }
2351
0
  }
2352
2353
0
  FR_SBUFF_SET_RETURN(out, &our_out);
2354
0
}