Coverage Report

Created: 2025-03-18 06:55

/src/wget2/libwget/buffer.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2012 Tim Ruehsen
3
 * Copyright (c) 2015-2024 Free Software Foundation, Inc.
4
 *
5
 * This file is part of libwget.
6
 *
7
 * Libwget is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Lesser General Public License as published by
9
 * the Free Software Foundation, either version 3 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * Libwget is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public License
18
 * along with libwget.  If not, see <https://www.gnu.org/licenses/>.
19
 *
20
 *
21
 * Memory buffer data structure routines
22
 *
23
 * Changelog
24
 * 22.08.2012  Tim Ruehsen  created
25
 *
26
 */
27
28
#include <config.h>
29
30
#include <stdio.h>
31
#include <stdlib.h>
32
#include <string.h>
33
#include <ctype.h>
34
35
#include <wget.h>
36
#include "private.h"
37
38
/**
39
 * \file
40
 * \brief Buffer management functions
41
 * \defgroup libwget-buffer Buffer management functions
42
 * @{
43
 *
44
 * A buffer (represented with an opaque `wget_buffer`) is a managed memory area.
45
 *
46
 * Apart from a pointer to a raw chunk of memory (`char *`), it also has some metadata attached
47
 * such as the length of the buffer and the actual occupied positions.
48
 *
49
 * Actually, when we talk about the **length** of the buffer, we refer to the actual number of bytes stored
50
 * in it by the user. On the other hand, the **size** is the total number of slots in the buffer, either occupied
51
 * or not.
52
 *
53
 * The stored data is always 0-terminated, so you safely use it with standard string functions.
54
 *
55
 * The functions here allow you to easily work with buffers, providing shortcuts to commonly used
56
 * memory and string management operations and avoiding usual pitfalls, such as buffer overflows.
57
 * They provide a higher-level abstraction to working with memory than the @link libwget-mem memory management functions@endlink.
58
 *
59
 * If your application uses memory allocation functions that may return %NULL values (e.g. like the standard libc functions),
60
 * you have to check the `error` value before using the result. If set, it indicates that a memory allocation failure
61
 * occurred during internal (re-)allocation.
62
 *
63
 * Example with wget_buffer on the stack (initial 16 bytes heap allocation)
64
 * ```
65
 * wget_buffer buf;
66
 *
67
 * wet_buffer_init(&buf, NULL, 16);
68
 *
69
 * wget_buffer_strcpy(&buf, "A");
70
 * wget_buffer_strcat(&buf, "B");
71
 * wget_buffer_memcat(&buf, "C", 1);
72
 * wget_buffer_memset_append(&buf, 'D', 1);
73
 * wget_buffer_printf_append(&buf, "%s", "E");
74
 *
75
 * // buf.data now contains the 0-terminated string "ABCDE"
76
 * printf("buf.data = %s\n", buf.data);
77
 *
78
 * wget_buffer_deinit(&buf);
79
 * ```
80
 *
81
 * Example with wget_buffer on the stack and 16 bytes initial stack buffer (no heap allocation is needed)
82
 * ```
83
 * wget_buffer buf;
84
 * char sbuf[16];
85
 *
86
 * wet_buffer_init(&buf, sbuf, sizeof(sbuf));
87
 *
88
 * wget_buffer_strcpy(&buf, "A");
89
 * wget_buffer_strcat(&buf, "B");
90
 * wget_buffer_memcat(&buf, "C", 1);
91
 * wget_buffer_memset_append(&buf, 'D', 1);
92
 * wget_buffer_printf_append(&buf, "%s", "E");
93
 *
94
 * // buf.data now contains the 0-terminated string "ABCDE"
95
 * printf("buf.data = %s\n", buf.data);
96
 *
97
 * wget_buffer_deinit(&buf);
98
 * ```
99
 *
100
 * Example on how to check for memory failure and how to transfer buffer data
101
 * ```
102
 * wget_buffer buf;
103
 *
104
 * wet_buffer_init(&buf, NULL, 16);
105
 *
106
 * wget_buffer_strcpy(&buf, "A");
107
 * wget_buffer_strcat(&buf, "B");
108
 * wget_buffer_memcat(&buf, "C", 1);
109
 * wget_buffer_memset_append(&buf, 'D', 1);
110
 * wget_buffer_printf_append(&buf, "%s", "E");
111
 *
112
 *  if (buf.error)
113
 *    panic("No memory !");
114
 *
115
 *  // transfer ownership away from wget_buffer
116
 *  char *ret = buf.data;
117
 *  buf.data = NULL; // avoid double frees
118
 *
119
 * wget_buffer_deinit(&buf);
120
 *
121
 *  return ret; // the caller must free() this value after use
122
 * ```
123
 */
124
125
/**
126
 * \param[in] buf Pointer to the buffer that should become initialized.
127
 * \param[in] data Initial contents of the buffer. Might be NULL.
128
 * \param[in] size Initial length of the buffer. Might be zero (will default to 128 bytes).
129
 * \return WGET_E_SUCCESS or WGET_E_MEMORY in case of a memory allocation failure.
130
 *
131
 * Create a new buffer.
132
 *
133
 * If \p data is NULL, the buffer will be empty, but it will be pre-allocated with \p size bytes.
134
 * This will make future operations on the buffer faster since there will be less re-allocations needed.
135
 *
136
 * <b>If \p size is zero, the buffer will be pre-allocated with 128 bytes.</b>
137
 *
138
 * You may provide some \p data to fill the buffer with it. The contents of the \p data pointer
139
 * are not copied, but rather the pointer itself is referenced directly within the buffer. If you modify the contents
140
 * of \p data, those changes will be reflected in the buffer as they both point to the same memory area.
141
 *
142
 * Apart from that, there are other concerns you should keep in mind if you provide your own \p data here:
143
 *
144
 *  - wget_buffer_deinit() _will not_ free that memory when you call it. So if you provide
145
 * a \p data pointer, you must free it yourself before your program ends.
146
 *  - wget_buffer_realloc() will also not free that memory. It will allocate a new buffer and copy the contents
147
 *  there, but will not touch the old buffer. The new buffer _will_ be freed by these functions since it's been
148
 *  allocated by libwget internally and thus it knows it can be freed without harm.
149
 *
150
 * If an existing buffer is provided in \p buf, it will be initialized with the provided \p data and \p size
151
 * according to the rules stated above.
152
 */
153
int wget_buffer_init(wget_buffer *buf, char *data, size_t size)
154
557k
{
155
557k
  if (data && likely(size)) {
156
442k
    buf->size = size - 1;
157
442k
    buf->data = data;
158
442k
    *buf->data = 0; // always 0 terminate data to allow string functions
159
442k
    buf->release_data = 0;
160
442k
  } else {
161
115k
    if (!size)
162
0
      size = 127;
163
115k
    buf->size = size;
164
115k
    if (!(buf->data = wget_malloc(size + 1))) {
165
0
      buf->error = 1;
166
0
      return WGET_E_MEMORY;
167
0
    }
168
115k
    *buf->data = 0; // always 0 terminate data to allow string functions
169
115k
    buf->release_data = 1;
170
115k
  }
171
172
557k
  buf->error = 0;
173
557k
  buf->release_buf = 0;
174
557k
  buf->length = 0;
175
176
557k
  return WGET_E_SUCCESS;
177
557k
}
178
179
/**
180
 * \param[in] size Initial length of the buffer.
181
 * \return A new buffer.
182
 *
183
 * Allocates a new buffer of size \p size bytes.
184
 *
185
 * The buffer will be pre-allocated with that many bytes, all zeros.
186
 *
187
 * This is equivalent to wget_buffer_init(NULL, NULL, size).
188
 */
189
wget_buffer *wget_buffer_alloc(size_t size)
190
11.2k
{
191
11.2k
  wget_buffer *buf;
192
193
11.2k
  if (!(buf = wget_malloc(sizeof(wget_buffer))))
194
0
    return NULL;
195
196
11.2k
  if (wget_buffer_init(buf, NULL, size) < 0) {
197
0
    xfree(buf);
198
0
    return NULL;
199
0
  }
200
201
11.2k
  buf->release_buf = 1;
202
203
11.2k
  return buf;
204
11.2k
}
205
206
static int buffer_realloc(wget_buffer *buf, size_t size)
207
364k
{
208
364k
  char *old_data = buf->data;
209
210
364k
  if (buf->release_data)
211
53.3k
    buf->data = wget_realloc(buf->data, size + 1);
212
311k
  else
213
311k
    buf->data = wget_malloc(size + 1);
214
215
364k
  if (!buf->data) {
216
0
    buf->data = old_data;
217
0
    buf->error = 1;
218
0
    return WGET_E_MEMORY;
219
0
  }
220
221
364k
  if (!buf->release_data) {
222
311k
    if (likely(old_data) && buf->length)
223
1.29k
      memcpy(buf->data, old_data, buf->length + 1);
224
310k
    else
225
310k
      *buf->data = 0; // always 0 terminate data to allow string functions
226
227
311k
    buf->release_data = 1;
228
311k
  }
229
230
364k
  buf->size = size;
231
232
364k
  return WGET_E_SUCCESS;
233
364k
}
234
235
/**
236
 * \param[in] buf A buffer, created with wget_buffer_init() or wget_buffer_alloc()
237
 * \param[in] size Total size (in bytes) required in the buffer
238
 * \return WGET_E_SUCCESS on success, else WGET_E_MEMORY if the memory allocation failed
239
 *
240
 * Make sure the buffer \p buf has at least a **size** of \p size bytes.
241
 *
242
 * If the buffer's size is less than that, it will automatically enlarge it
243
 * (with wget_buffer_realloc()) to make it at least as long.
244
 */
245
int wget_buffer_ensure_capacity(wget_buffer *buf, size_t size)
246
0
{
247
0
  if (likely(buf)) {
248
0
    if (buf->size < size)
249
0
      return buffer_realloc(buf, size);
250
0
  }
251
252
0
  return WGET_E_SUCCESS;
253
0
}
254
255
/**
256
 * \param[in] buf A buffer, created with wget_buffer_init() or wget_buffer_alloc()
257
 *
258
 * Free the buffer, and all its contents.
259
 *
260
 * If you provided your own data when calling wget_buffer_init() (you passed a non-NULL \p data pointer)
261
 * then **that buffer will not be freed**. As stated in the description of wget_buffer_init() you
262
 * must free that buffer yourself: this function will only free the `wget_buffer` structure.
263
 *
264
 * Similarly, if you provided your own buffer when calling wget_buffer_init() (\p buf was non-NULL)
265
 * the buffer (the `wget_buffer` structure) **will not** be freed, and the data might or might not be freed
266
 * depending on the above condition.
267
 */
268
void wget_buffer_deinit(wget_buffer *buf)
269
465k
{
270
465k
  if (buf->release_data) {
271
334k
    xfree(buf->data);
272
334k
    buf->release_data = 0;
273
334k
  }
274
275
465k
  if (buf->release_buf)
276
11.2k
    wget_free(buf); // do not use xfree() since buf is NONNULL
277
465k
}
278
279
/**
280
 * \param[in] buf A double pointer to a buffer
281
 *
282
 * Free the buffer, and all its contents.
283
 *
284
 * It behaves like wget_buffer_deinit() but it also sets the \p buf pointer to NULL.
285
 *
286
 * This function is equivalent to:
287
 *
288
 *     wget_buffer_deinit(*buf);
289
 *     *buf = NULL;
290
 */
291
void wget_buffer_free(wget_buffer **buf)
292
22.9k
{
293
22.9k
  if (likely(buf && *buf)) {
294
11.2k
    wget_buffer_deinit(*buf);
295
11.2k
    *buf = NULL;
296
11.2k
  }
297
22.9k
}
298
299
/**
300
 * \param[in] buf A buffer, created with wget_buffer_init() or wget_buffer_alloc()
301
 *
302
 * Release the buffer's data, but keep the buffer itself (the `wget_buffer` structure).
303
 *
304
 * The **length** of the buffer will be maintained, but after this function succeeds, the **size**
305
 * will obviously be zero.
306
 *
307
 * The same rules that apply to wget_buffer_deinit() also apply here: if you provided your own data
308
 * when calling wget_buffer_init() (ie. \p data was non-NULL) then **that data will not be freed**, and this
309
 * function will essentially be a no-op.
310
 */
311
void wget_buffer_free_data(wget_buffer *buf)
312
0
{
313
0
  if (likely(buf)) {
314
0
    if (buf->release_data) {
315
0
      xfree(buf->data);
316
0
      buf->release_data = 0;
317
0
      buf->size = 0;
318
0
    }
319
0
  }
320
0
}
321
322
/**
323
 * \param[in] buf A buffer, created with wget_buffer_init() or wget_buffer_alloc()
324
 *
325
 * This function is lighter than wget_buffer_free_data(). It does not free the data buffer, it just
326
 * sets its first byte to zero, as well as the length.
327
 *
328
 * This function is equivalent to:
329
 *
330
 *     buf->length = 0;
331
 *     *buf->data = 0;
332
 */
333
void wget_buffer_reset(wget_buffer *buf)
334
27.9k
{
335
27.9k
  if (likely(buf)) {
336
27.9k
    buf->length = 0;
337
27.9k
    *buf->data = 0;
338
27.9k
  }
339
27.9k
}
340
341
/**
342
 * \param[in] buf  A buffer, created with wget_buffer_init() or wget_buffer_alloc()
343
 * \param[in] data A pointer to the data to be copied
344
 * \param[in] length How many bytes from \p data (starting at the beginning) should be copied
345
 * \return The new length of the buffer after copying the data
346
 *
347
 * Copy the contents in the pointer \p data to the buffer \p buf,
348
 * clobbering the previous contents.
349
 *
350
 * The first \p length bytes of \p data are written to \p buf.
351
 * The content of \p buf is overwritten with the new \p data.
352
 *
353
 * If the buffer is not large enough to store that amount of data,
354
 * it is enlarged automatically at least \p length bytes (with wget_buffer_realloc()).
355
 */
356
size_t wget_buffer_memcpy(wget_buffer *buf, const void *data, size_t length)
357
6.71k
{
358
6.71k
  if (unlikely(!buf))
359
0
    return 0;
360
361
6.71k
  buf->length = 0;
362
363
6.71k
  return wget_buffer_memcat(buf, data, length);
364
6.71k
}
365
366
/**
367
 * \param[in] buf A buffer, created with wget_buffer_init() or wget_buffer_alloc()
368
 * \param[in] data A pointer to the data to be appended
369
 * \param[in] length How many bytes of \p data should be written to \p buf
370
 * \return The new length of the buffer after appending the data
371
 *
372
 * Append the provided \p data to the end of the buffer \p buf (preserving contents).
373
 *
374
 * If there's not enough space in \p buf, it is enlarged automatically
375
 * (with wget_buffer_realloc()) at least \p length bytes, so that the whole
376
 * data can be written.
377
 */
378
size_t wget_buffer_memcat(wget_buffer *buf, const void *data, size_t length)
379
3.62M
{
380
3.62M
  if (unlikely(!buf))
381
0
    return 0;
382
383
3.62M
  if (likely(length)) {
384
3.56M
    if (buf->size < buf->length + length)
385
361k
      if (buffer_realloc(buf, buf->size * 2 + length) != WGET_E_SUCCESS)
386
0
        return buf->length;
387
388
3.56M
    if (likely(data))
389
3.56M
      memcpy(buf->data + buf->length, data, length);
390
0
    else
391
0
      memset(buf->data + buf->length, 0, length);
392
3.56M
    buf->length += length;
393
3.56M
  }
394
3.62M
  buf->data[buf->length] = 0; // always 0 terminate data to allow string functions
395
396
3.62M
  return buf->length;
397
3.62M
}
398
399
/**
400
 * \param[in] buf A buffer, created with wget_buffer_init() or wget_buffer_alloc()
401
 * \param[in] s A NULL-terminated string
402
 * \return The new length of the buffer after copying the string
403
 *
404
 * Copy the NULL-terminated string \p s to the buffer \p buf,
405
 * overwriting its original contents.
406
 *
407
 * If the buffer is not large enough it is enlarged automatically.
408
 *
409
 * This is essentially equivalent to:
410
 *
411
 *     buf->length = 0;
412
 *     wget_buffer_memcat(buf, s, strlen(s));
413
 */
414
size_t wget_buffer_strcpy(wget_buffer *buf, const char *s)
415
5.59k
{
416
5.59k
  if (likely(buf))
417
5.59k
    buf->length = 0;
418
419
5.59k
  return wget_buffer_memcat(buf, s, likely(s) ? strlen(s) : 0);
420
5.59k
}
421
422
/**
423
 * \param[in] buf A buffer, created with wget_buffer_init() or wget_buffer_alloc()
424
 * \param[in] s A NULL-terminated string
425
 * \return The new length of the buffer after appending the string
426
 *
427
 * Append the NULL-terminated string \p s to the end of the buffer  \p buf
428
 * (preserving its contents).
429
 *
430
 * If the buffer is not large enough it is enlarged automatically.
431
 *
432
 * This is essentially equivalent to calling wget_buffer_memcat() with length equal to `strlen(s)`:
433
 *
434
 *     wget_buffer_memcat(buf, s, strlen(s));
435
 */
436
size_t wget_buffer_strcat(wget_buffer *buf, const char *s)
437
285k
{
438
285k
  return wget_buffer_memcat(buf, s, likely(s) ? strlen(s) : 0);
439
285k
}
440
441
/**
442
 * \param[in] buf The destination buffer
443
 * \param[in] src The source buffer
444
 * \return The new length of the destination buffer \p buf after copying the contents of \p src
445
 *
446
 * Copy the contents of the buffer \p src in the buffer \p buf,
447
 * clobbering its previous contents.
448
 *
449
 * If the buffer \p buf is not large enough it is enlarged automatically.
450
 *
451
 * This is equivalent to:
452
 *
453
 *     wget_buffer_memcpy(buf, src->data, src->length);
454
 */
455
size_t wget_buffer_bufcpy(wget_buffer *buf, wget_buffer *src)
456
0
{
457
0
  if (likely(src))
458
0
    return wget_buffer_memcpy(buf, src->data, src->length);
459
0
  else
460
0
    return wget_buffer_memcpy(buf, NULL, 0);
461
0
}
462
463
/**
464
 * \param[in] buf The destination buffer
465
 * \param[in] src The source buffer
466
 * \return The new length of the destination buffer \p buf after appending the contents of \p src
467
 *
468
 * Append the contents of the buffer \p src to the end of the buffer \p buf.
469
 *
470
 * If the buffer \p buf is not large enough it is enlarged automatically.
471
 *
472
 * This is equivalent to:
473
 *
474
 *     wget_buffer_memcat(buf, src->data, src->length);
475
 */
476
size_t wget_buffer_bufcat(wget_buffer *buf, wget_buffer *src)
477
3.84k
{
478
3.84k
  if (likely(src))
479
3.84k
    return wget_buffer_memcat(buf, src->data, src->length);
480
0
  else
481
0
    return wget_buffer_memcat(buf, NULL, 0);
482
3.84k
}
483
484
/**
485
 * \param[in] buf A buffer, created with wget_buffer_init() or wget_buffer_alloc()
486
 * \param[in] c The byte to be copied at the end of the buffer
487
 * \param[in] length How many times will the byte \p c be copied.
488
 * \return The new length of the buffer \p buf.
489
 *
490
 * Copy the byte \p c repeatedly \p length times **starting at the beginning of the buffer**,
491
 * so the first \p length bytes of the buffer are overwritten.
492
 *
493
 * If there's not enough space in \p buf, it is enlarged automatically
494
 * (with wget_buffer_realloc()) at least \p length bytes.
495
 */
496
size_t wget_buffer_memset(wget_buffer *buf, char c, size_t length)
497
0
{
498
0
  if (likely(buf))
499
0
    buf->length = 0;
500
501
0
  return wget_buffer_memset_append(buf, c, length);
502
0
}
503
504
/**
505
 * \param[in] buf A buffer, created with wget_buffer_init() or wget_buffer_alloc()
506
 * \param[in] c The byte to be copied at the end of the buffer
507
 * \param[in] length How many times will the byte \p c be copied.
508
 * \return The new length of the buffer \p buf.
509
 *
510
 * Copy the byte \p c at the end of the buffer \p buf repeatedly \p length times.
511
 *
512
 * If there's not enough space in \p buf, it is enlarged automatically
513
 * (with wget_buffer_realloc()) at least \p length bytes.
514
 */
515
size_t wget_buffer_memset_append(wget_buffer *buf, char c, size_t length)
516
2.46M
{
517
2.46M
  if (unlikely(!buf))
518
0
    return 0;
519
520
2.46M
  if (likely(length)) {
521
2.46M
    if (unlikely(buf->size < buf->length + length))
522
3.71k
      if (buffer_realloc(buf, buf->size * 2 + length) != WGET_E_SUCCESS)
523
0
        return buf->length;
524
525
2.46M
    memset(buf->data + buf->length, c, length);
526
2.46M
    buf->length += length;
527
2.46M
  }
528
2.46M
  buf->data[buf->length] = 0; // always 0 terminate data to allow string functions
529
530
2.46M
  return buf->length;
531
2.46M
}
532
533
/**
534
 * \param[in] buf A buffer, created with wget_buffer_init() or wget_buffer_alloc()
535
 * \return The buffer's new contents
536
 *
537
 * Remove all leading and trailing whitespace from the buffer \p buf.
538
 *
539
 * The transformation is done in-place, that is, the buffer's original content is overwritten
540
 * with the new trimmed content.
541
 */
542
char *wget_buffer_trim(wget_buffer *buf)
543
0
{
544
0
  if (unlikely(!buf))
545
0
    return NULL;
546
547
0
  while (buf->length > 0 && isspace(buf->data[buf->length - 1])) {
548
0
    buf->length--;
549
0
  }
550
0
  buf->data[buf->length] = 0;
551
552
0
  size_t spaces = 0;
553
0
  while (buf->length > 0 && isspace(buf->data[spaces]))
554
0
    spaces++;
555
556
0
  if (spaces) {
557
0
    buf->length -= spaces;
558
    /* Include trailing 0 */
559
0
    memmove(buf->data, buf->data + spaces, buf->length + 1);
560
0
  }
561
562
0
  return buf->data;
563
0
}
564
/** @} */