Coverage Report

Created: 2025-08-26 07:08

/src/PROJ/curl/lib/bufq.c
Line
Count
Source (jump to first uncovered line)
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
25
#include "curl_setup.h"
26
#include "bufq.h"
27
28
/* The last 3 #include files should be in this order */
29
#include "curl_printf.h"
30
#include "curl_memory.h"
31
#include "memdebug.h"
32
33
static bool chunk_is_empty(const struct buf_chunk *chunk)
34
0
{
35
0
  return chunk->r_offset >= chunk->w_offset;
36
0
}
37
38
static bool chunk_is_full(const struct buf_chunk *chunk)
39
0
{
40
0
  return chunk->w_offset >= chunk->dlen;
41
0
}
42
43
static size_t chunk_len(const struct buf_chunk *chunk)
44
0
{
45
0
  return chunk->w_offset - chunk->r_offset;
46
0
}
47
48
static void chunk_reset(struct buf_chunk *chunk)
49
0
{
50
0
  chunk->next = NULL;
51
0
  chunk->r_offset = chunk->w_offset = 0;
52
0
}
53
54
static size_t chunk_append(struct buf_chunk *chunk,
55
                           const unsigned char *buf, size_t len)
56
0
{
57
0
  unsigned char *p = &chunk->x.data[chunk->w_offset];
58
0
  size_t n = chunk->dlen - chunk->w_offset;
59
0
  DEBUGASSERT(chunk->dlen >= chunk->w_offset);
60
0
  if(n) {
61
0
    n = CURLMIN(n, len);
62
0
    memcpy(p, buf, n);
63
0
    chunk->w_offset += n;
64
0
  }
65
0
  return n;
66
0
}
67
68
static size_t chunk_read(struct buf_chunk *chunk,
69
                         unsigned char *buf, size_t len)
70
0
{
71
0
  unsigned char *p = &chunk->x.data[chunk->r_offset];
72
0
  size_t n = chunk->w_offset - chunk->r_offset;
73
0
  DEBUGASSERT(chunk->w_offset >= chunk->r_offset);
74
0
  if(!n) {
75
0
    return 0;
76
0
  }
77
0
  else if(n <= len) {
78
0
    memcpy(buf, p, n);
79
0
    chunk->r_offset = chunk->w_offset = 0;
80
0
    return n;
81
0
  }
82
0
  else {
83
0
    memcpy(buf, p, len);
84
0
    chunk->r_offset += len;
85
0
    return len;
86
0
  }
87
0
}
88
89
static CURLcode chunk_slurpn(struct buf_chunk *chunk, size_t max_len,
90
                             Curl_bufq_reader *reader,
91
                             void *reader_ctx, size_t *pnread)
92
0
{
93
0
  unsigned char *p = &chunk->x.data[chunk->w_offset];
94
0
  size_t n = chunk->dlen - chunk->w_offset; /* free amount */
95
0
  CURLcode result;
96
97
0
  *pnread = 0;
98
0
  DEBUGASSERT(chunk->dlen >= chunk->w_offset);
99
0
  if(!n)
100
0
    return CURLE_AGAIN;
101
0
  if(max_len && n > max_len)
102
0
    n = max_len;
103
0
  result = reader(reader_ctx, p, n, pnread);
104
0
  if(!result) {
105
0
    DEBUGASSERT(*pnread <= n);
106
0
    chunk->w_offset += *pnread;
107
0
  }
108
0
  return result;
109
0
}
110
111
static void chunk_peek(const struct buf_chunk *chunk,
112
                       const unsigned char **pbuf, size_t *plen)
113
0
{
114
0
  DEBUGASSERT(chunk->w_offset >= chunk->r_offset);
115
0
  *pbuf = &chunk->x.data[chunk->r_offset];
116
0
  *plen = chunk->w_offset - chunk->r_offset;
117
0
}
118
119
static void chunk_peek_at(const struct buf_chunk *chunk, size_t offset,
120
                          const unsigned char **pbuf, size_t *plen)
121
0
{
122
0
  offset += chunk->r_offset;
123
0
  DEBUGASSERT(chunk->w_offset >= offset);
124
0
  *pbuf = &chunk->x.data[offset];
125
0
  *plen = chunk->w_offset - offset;
126
0
}
127
128
static size_t chunk_skip(struct buf_chunk *chunk, size_t amount)
129
0
{
130
0
  size_t n = chunk->w_offset - chunk->r_offset;
131
0
  DEBUGASSERT(chunk->w_offset >= chunk->r_offset);
132
0
  if(n) {
133
0
    n = CURLMIN(n, amount);
134
0
    chunk->r_offset += n;
135
0
    if(chunk->r_offset == chunk->w_offset)
136
0
      chunk->r_offset = chunk->w_offset = 0;
137
0
  }
138
0
  return n;
139
0
}
140
141
static void chunk_list_free(struct buf_chunk **anchor)
142
0
{
143
0
  struct buf_chunk *chunk;
144
0
  while(*anchor) {
145
0
    chunk = *anchor;
146
0
    *anchor = chunk->next;
147
0
    free(chunk);
148
0
  }
149
0
}
150
151
152
153
void Curl_bufcp_init(struct bufc_pool *pool,
154
                     size_t chunk_size, size_t spare_max)
155
0
{
156
0
  DEBUGASSERT(chunk_size > 0);
157
0
  DEBUGASSERT(spare_max > 0);
158
0
  memset(pool, 0, sizeof(*pool));
159
0
  pool->chunk_size = chunk_size;
160
0
  pool->spare_max = spare_max;
161
0
}
162
163
static CURLcode bufcp_take(struct bufc_pool *pool,
164
                           struct buf_chunk **pchunk)
165
0
{
166
0
  struct buf_chunk *chunk = NULL;
167
168
0
  if(pool->spare) {
169
0
    chunk = pool->spare;
170
0
    pool->spare = chunk->next;
171
0
    --pool->spare_count;
172
0
    chunk_reset(chunk);
173
0
    *pchunk = chunk;
174
0
    return CURLE_OK;
175
0
  }
176
177
  /* Check for integer overflow before allocation */
178
0
  if(pool->chunk_size > SIZE_MAX - sizeof(*chunk)) {
179
0
    *pchunk = NULL;
180
0
    return CURLE_OUT_OF_MEMORY;
181
0
  }
182
183
0
  chunk = calloc(1, sizeof(*chunk) + pool->chunk_size);
184
0
  if(!chunk) {
185
0
    *pchunk = NULL;
186
0
    return CURLE_OUT_OF_MEMORY;
187
0
  }
188
0
  chunk->dlen = pool->chunk_size;
189
0
  *pchunk = chunk;
190
0
  return CURLE_OK;
191
0
}
192
193
static void bufcp_put(struct bufc_pool *pool,
194
                      struct buf_chunk *chunk)
195
0
{
196
0
  if(pool->spare_count >= pool->spare_max) {
197
0
    free(chunk);
198
0
  }
199
0
  else {
200
0
    chunk_reset(chunk);
201
0
    chunk->next = pool->spare;
202
0
    pool->spare = chunk;
203
0
    ++pool->spare_count;
204
0
  }
205
0
}
206
207
void Curl_bufcp_free(struct bufc_pool *pool)
208
0
{
209
0
  chunk_list_free(&pool->spare);
210
0
  pool->spare_count = 0;
211
0
}
212
213
static void bufq_init(struct bufq *q, struct bufc_pool *pool,
214
                      size_t chunk_size, size_t max_chunks, int opts)
215
0
{
216
0
  DEBUGASSERT(chunk_size > 0);
217
0
  DEBUGASSERT(max_chunks > 0);
218
0
  memset(q, 0, sizeof(*q));
219
0
  q->chunk_size = chunk_size;
220
0
  q->max_chunks = max_chunks;
221
0
  q->pool = pool;
222
0
  q->opts = opts;
223
0
}
224
225
void Curl_bufq_init2(struct bufq *q, size_t chunk_size, size_t max_chunks,
226
                     int opts)
227
0
{
228
0
  bufq_init(q, NULL, chunk_size, max_chunks, opts);
229
0
}
230
231
void Curl_bufq_init(struct bufq *q, size_t chunk_size, size_t max_chunks)
232
0
{
233
0
  bufq_init(q, NULL, chunk_size, max_chunks, BUFQ_OPT_NONE);
234
0
}
235
236
void Curl_bufq_initp(struct bufq *q, struct bufc_pool *pool,
237
                     size_t max_chunks, int opts)
238
0
{
239
0
  bufq_init(q, pool, pool->chunk_size, max_chunks, opts);
240
0
}
241
242
void Curl_bufq_free(struct bufq *q)
243
0
{
244
0
  chunk_list_free(&q->head);
245
0
  chunk_list_free(&q->spare);
246
0
  q->tail = NULL;
247
0
  q->chunk_count = 0;
248
0
}
249
250
void Curl_bufq_reset(struct bufq *q)
251
0
{
252
0
  struct buf_chunk *chunk;
253
0
  while(q->head) {
254
0
    chunk = q->head;
255
0
    q->head = chunk->next;
256
0
    chunk->next = q->spare;
257
0
    q->spare = chunk;
258
0
  }
259
0
  q->tail = NULL;
260
0
}
261
262
size_t Curl_bufq_len(const struct bufq *q)
263
0
{
264
0
  const struct buf_chunk *chunk = q->head;
265
0
  size_t len = 0;
266
0
  while(chunk) {
267
0
    len += chunk_len(chunk);
268
0
    chunk = chunk->next;
269
0
  }
270
0
  return len;
271
0
}
272
273
bool Curl_bufq_is_empty(const struct bufq *q)
274
0
{
275
0
  return !q->head || chunk_is_empty(q->head);
276
0
}
277
278
bool Curl_bufq_is_full(const struct bufq *q)
279
0
{
280
0
  if(!q->tail || q->spare)
281
0
    return FALSE;
282
0
  if(q->chunk_count < q->max_chunks)
283
0
    return FALSE;
284
0
  if(q->chunk_count > q->max_chunks)
285
0
    return TRUE;
286
  /* we have no spares and cannot make more, is the tail full? */
287
0
  return chunk_is_full(q->tail);
288
0
}
289
290
static struct buf_chunk *get_spare(struct bufq *q)
291
0
{
292
0
  struct buf_chunk *chunk = NULL;
293
294
0
  if(q->spare) {
295
0
    chunk = q->spare;
296
0
    q->spare = chunk->next;
297
0
    chunk_reset(chunk);
298
0
    return chunk;
299
0
  }
300
301
0
  if(q->chunk_count >= q->max_chunks && (!(q->opts & BUFQ_OPT_SOFT_LIMIT)))
302
0
    return NULL;
303
304
0
  if(q->pool) {
305
0
    if(bufcp_take(q->pool, &chunk))
306
0
      return NULL;
307
0
    ++q->chunk_count;
308
0
    return chunk;
309
0
  }
310
0
  else {
311
    /* Check for integer overflow before allocation */
312
0
    if(q->chunk_size > SIZE_MAX - sizeof(*chunk)) {
313
0
      return NULL;
314
0
    }
315
316
0
    chunk = calloc(1, sizeof(*chunk) + q->chunk_size);
317
0
    if(!chunk)
318
0
      return NULL;
319
0
    chunk->dlen = q->chunk_size;
320
0
    ++q->chunk_count;
321
0
    return chunk;
322
0
  }
323
0
}
324
325
static void prune_head(struct bufq *q)
326
0
{
327
0
  struct buf_chunk *chunk;
328
329
0
  while(q->head && chunk_is_empty(q->head)) {
330
0
    chunk = q->head;
331
0
    q->head = chunk->next;
332
0
    if(q->tail == chunk)
333
0
      q->tail = q->head;
334
0
    if(q->pool) {
335
0
      bufcp_put(q->pool, chunk);
336
0
      --q->chunk_count;
337
0
    }
338
0
    else if((q->chunk_count > q->max_chunks) ||
339
0
       (q->opts & BUFQ_OPT_NO_SPARES)) {
340
      /* SOFT_LIMIT allowed us more than max. free spares until
341
       * we are at max again. Or free them if we are configured
342
       * to not use spares. */
343
0
      free(chunk);
344
0
      --q->chunk_count;
345
0
    }
346
0
    else {
347
0
      chunk->next = q->spare;
348
0
      q->spare = chunk;
349
0
    }
350
0
  }
351
0
}
352
353
static struct buf_chunk *get_non_full_tail(struct bufq *q)
354
0
{
355
0
  struct buf_chunk *chunk;
356
357
0
  if(q->tail && !chunk_is_full(q->tail))
358
0
    return q->tail;
359
0
  chunk = get_spare(q);
360
0
  if(chunk) {
361
    /* new tail, and possibly new head */
362
0
    if(q->tail) {
363
0
      q->tail->next = chunk;
364
0
      q->tail = chunk;
365
0
    }
366
0
    else {
367
0
      DEBUGASSERT(!q->head);
368
0
      q->head = q->tail = chunk;
369
0
    }
370
0
  }
371
0
  return chunk;
372
0
}
373
374
CURLcode Curl_bufq_write(struct bufq *q,
375
                         const unsigned char *buf, size_t len,
376
                         size_t *pnwritten)
377
0
{
378
0
  struct buf_chunk *tail;
379
0
  size_t n;
380
381
0
  DEBUGASSERT(q->max_chunks > 0);
382
0
  *pnwritten = 0;
383
0
  while(len) {
384
0
    tail = get_non_full_tail(q);
385
0
    if(!tail) {
386
0
      if((q->chunk_count < q->max_chunks) || (q->opts & BUFQ_OPT_SOFT_LIMIT))
387
        /* should have gotten a tail, but did not */
388
0
        return CURLE_OUT_OF_MEMORY;
389
0
      break;
390
0
    }
391
0
    n = chunk_append(tail, buf, len);
392
0
    if(!n)
393
0
      break;
394
0
    *pnwritten += n;
395
0
    buf += n;
396
0
    len -= n;
397
0
  }
398
0
  return (!*pnwritten && len) ? CURLE_AGAIN : CURLE_OK;
399
0
}
400
401
CURLcode Curl_bufq_cwrite(struct bufq *q,
402
                          const char *buf, size_t len,
403
                          size_t *pnwritten)
404
0
{
405
0
  return Curl_bufq_write(q, (const unsigned char *)buf, len, pnwritten);
406
0
}
407
408
CURLcode Curl_bufq_read(struct bufq *q, unsigned char *buf, size_t len,
409
                        size_t *pnread)
410
0
{
411
0
  *pnread = 0;
412
0
  while(len && q->head) {
413
0
    size_t n = chunk_read(q->head, buf, len);
414
0
    if(n) {
415
0
      *pnread += n;
416
0
      buf += n;
417
0
      len -= n;
418
0
    }
419
0
    prune_head(q);
420
0
  }
421
0
  return (!*pnread) ? CURLE_AGAIN : CURLE_OK;
422
0
}
423
424
CURLcode Curl_bufq_cread(struct bufq *q, char *buf, size_t len,
425
                         size_t *pnread)
426
0
{
427
0
  return Curl_bufq_read(q, (unsigned char *)buf, len, pnread);
428
0
}
429
430
bool Curl_bufq_peek(struct bufq *q,
431
                    const unsigned char **pbuf, size_t *plen)
432
0
{
433
0
  if(q->head && chunk_is_empty(q->head)) {
434
0
    prune_head(q);
435
0
  }
436
0
  if(q->head && !chunk_is_empty(q->head)) {
437
0
    chunk_peek(q->head, pbuf, plen);
438
0
    return TRUE;
439
0
  }
440
0
  *pbuf = NULL;
441
0
  *plen = 0;
442
0
  return FALSE;
443
0
}
444
445
bool Curl_bufq_peek_at(struct bufq *q, size_t offset,
446
                       const unsigned char **pbuf, size_t *plen)
447
0
{
448
0
  struct buf_chunk *c = q->head;
449
0
  size_t clen;
450
451
0
  while(c) {
452
0
    clen = chunk_len(c);
453
0
    if(!clen)
454
0
      break;
455
0
    if(offset >= clen) {
456
0
      offset -= clen;
457
0
      c = c->next;
458
0
      continue;
459
0
    }
460
0
    chunk_peek_at(c, offset, pbuf, plen);
461
0
    return TRUE;
462
0
  }
463
0
  *pbuf = NULL;
464
0
  *plen = 0;
465
0
  return FALSE;
466
0
}
467
468
void Curl_bufq_skip(struct bufq *q, size_t amount)
469
0
{
470
0
  size_t n;
471
472
0
  while(amount && q->head) {
473
0
    n = chunk_skip(q->head, amount);
474
0
    amount -= n;
475
0
    prune_head(q);
476
0
  }
477
0
}
478
479
CURLcode Curl_bufq_pass(struct bufq *q, Curl_bufq_writer *writer,
480
                        void *writer_ctx, size_t *pwritten)
481
0
{
482
0
  const unsigned char *buf;
483
0
  size_t blen;
484
0
  CURLcode result = CURLE_OK;
485
486
0
  *pwritten = 0;
487
0
  while(Curl_bufq_peek(q, &buf, &blen)) {
488
0
    size_t chunk_written;
489
490
0
    result = writer(writer_ctx, buf, blen, &chunk_written);
491
0
    if(result) {
492
0
      if((result == CURLE_AGAIN) && *pwritten) {
493
        /* blocked on subsequent write, report success */
494
0
        result = CURLE_OK;
495
0
      }
496
0
      break;
497
0
    }
498
0
    if(!chunk_written) {
499
0
      if(!*pwritten) {
500
        /* treat as blocked */
501
0
        result = CURLE_AGAIN;
502
0
      }
503
0
      break;
504
0
    }
505
0
    *pwritten += chunk_written;
506
0
    Curl_bufq_skip(q, chunk_written);
507
0
  }
508
0
  return result;
509
0
}
510
511
CURLcode Curl_bufq_write_pass(struct bufq *q,
512
                              const unsigned char *buf, size_t len,
513
                              Curl_bufq_writer *writer, void *writer_ctx,
514
                              size_t *pwritten)
515
0
{
516
0
  CURLcode result = CURLE_OK;
517
0
  size_t n;
518
519
0
  *pwritten = 0;
520
0
  while(len) {
521
0
    if(Curl_bufq_is_full(q)) {
522
      /* try to make room in case we are full */
523
0
      result = Curl_bufq_pass(q, writer, writer_ctx, &n);
524
0
      if(result) {
525
0
        if(result != CURLE_AGAIN) {
526
          /* real error, fail */
527
0
          return result;
528
0
        }
529
        /* would block, bufq is full, give up */
530
0
        break;
531
0
      }
532
0
    }
533
534
    /* Add to bufq as much as there is room for */
535
0
    result = Curl_bufq_write(q, buf, len, &n);
536
0
    if(result) {
537
0
      if(result != CURLE_AGAIN)
538
        /* real error, fail */
539
0
        return result;
540
      /* result == CURLE_AGAIN */
541
0
      if(*pwritten)
542
        /* we did write successfully before */
543
0
        result = CURLE_OK;
544
0
      return result;
545
0
    }
546
0
    else if(n == 0)
547
      /* edge case of writer returning 0 (and len is >0)
548
       * break or we might enter an infinite loop here */
549
0
      break;
550
551
    /* Track what we added to bufq */
552
0
    buf += n;
553
0
    len -= n;
554
0
    *pwritten += n;
555
0
  }
556
557
0
  return (!*pwritten && len) ? CURLE_AGAIN : CURLE_OK;
558
0
}
559
560
CURLcode Curl_bufq_sipn(struct bufq *q, size_t max_len,
561
                        Curl_bufq_reader *reader, void *reader_ctx,
562
                        size_t *pnread)
563
0
{
564
0
  struct buf_chunk *tail = NULL;
565
566
0
  *pnread = 0;
567
0
  tail = get_non_full_tail(q);
568
0
  if(!tail) {
569
0
    if(q->chunk_count < q->max_chunks)
570
0
      return CURLE_OUT_OF_MEMORY;
571
    /* full, blocked */
572
0
    return CURLE_AGAIN;
573
0
  }
574
575
0
  return chunk_slurpn(tail, max_len, reader, reader_ctx, pnread);
576
0
}
577
578
/**
579
 * Read up to `max_len` bytes and append it to the end of the buffer queue.
580
 * if `max_len` is 0, no limit is imposed and the call behaves exactly
581
 * the same as `Curl_bufq_slurp()`.
582
 * Returns the total amount of buf read (may be 0) in `pnread` or error
583
 * Note that even in case of an error chunks may have been read and
584
 * the buffer queue will have different length than before.
585
 */
586
static CURLcode bufq_slurpn(struct bufq *q, size_t max_len,
587
                            Curl_bufq_reader *reader, void *reader_ctx,
588
                            size_t *pnread)
589
0
{
590
0
  CURLcode result;
591
592
0
  *pnread = 0;
593
0
  while(1) {
594
0
    size_t n;
595
0
    result = Curl_bufq_sipn(q, max_len, reader, reader_ctx, &n);
596
0
    if(result) {
597
0
      if(!*pnread || result != CURLE_AGAIN) {
598
        /* blocked on first read or real error, fail */
599
0
        return result;
600
0
      }
601
0
      result = CURLE_OK;
602
0
      break;
603
0
    }
604
0
    else if(n == 0) {
605
      /* eof, result remains CURLE_OK */
606
0
      break;
607
0
    }
608
0
    *pnread += n;
609
0
    if(max_len) {
610
0
      DEBUGASSERT(n <= max_len);
611
0
      max_len -= n;
612
0
      if(!max_len)
613
0
        break;
614
0
    }
615
    /* give up slurping when we get less bytes than we asked for */
616
0
    if(q->tail && !chunk_is_full(q->tail))
617
0
      break;
618
0
  }
619
0
  return result;
620
0
}
621
622
CURLcode Curl_bufq_slurp(struct bufq *q, Curl_bufq_reader *reader,
623
                         void *reader_ctx, size_t *pnread)
624
0
{
625
0
  return bufq_slurpn(q, 0, reader, reader_ctx, pnread);
626
0
}