Coverage Report

Created: 2025-12-03 07:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/headers.c
Line
Count
Source
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
27
#include "urldata.h"
28
#include "strdup.h"
29
#include "sendf.h"
30
#include "headers.h"
31
#include "curlx/strparse.h"
32
33
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HEADERS_API)
34
35
/* Generate the curl_header struct for the user. This function MUST assign all
36
   struct fields in the output struct. */
37
static void copy_header_external(struct Curl_header_store *hs,
38
                                 size_t index,
39
                                 size_t amount,
40
                                 struct Curl_llist_node *e,
41
                                 struct curl_header *hout)
42
0
{
43
0
  struct curl_header *h = hout;
44
0
  h->name = hs->name;
45
0
  h->value = hs->value;
46
0
  h->amount = amount;
47
0
  h->index = index;
48
  /* this will randomly OR a reserved bit for the sole purpose of making it
49
     impossible for applications to do == comparisons, as that would otherwise
50
     be tempting and then lead to the reserved bits not being reserved
51
     anymore. */
52
0
  h->origin = (unsigned int)(hs->type | (1 << 27));
53
0
  h->anchor = e;
54
0
}
55
56
/* public API */
57
CURLHcode curl_easy_header(CURL *easy,
58
                           const char *name,
59
                           size_t nameindex,
60
                           unsigned int type,
61
                           int request,
62
                           struct curl_header **hout)
63
0
{
64
0
  struct Curl_llist_node *e;
65
0
  struct Curl_llist_node *e_pick = NULL;
66
0
  struct Curl_easy *data = easy;
67
0
  size_t match = 0;
68
0
  size_t amount = 0;
69
0
  struct Curl_header_store *hs = NULL;
70
0
  struct Curl_header_store *pick = NULL;
71
0
  if(!name || !hout || !data ||
72
0
     (type > (CURLH_HEADER|CURLH_TRAILER|CURLH_CONNECT|CURLH_1XX|
73
0
              CURLH_PSEUDO)) || !type || (request < -1))
74
0
    return CURLHE_BAD_ARGUMENT;
75
0
  if(!Curl_llist_count(&data->state.httphdrs))
76
0
    return CURLHE_NOHEADERS; /* no headers available */
77
0
  if(request > data->state.requests)
78
0
    return CURLHE_NOREQUEST;
79
0
  if(request == -1)
80
0
    request = data->state.requests;
81
82
  /* we need a first round to count amount of this header */
83
0
  for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) {
84
0
    hs = Curl_node_elem(e);
85
0
    if(curl_strequal(hs->name, name) &&
86
0
       (hs->type & type) &&
87
0
       (hs->request == request)) {
88
0
      amount++;
89
0
      pick = hs;
90
0
      e_pick = e;
91
0
    }
92
0
  }
93
0
  if(!amount)
94
0
    return CURLHE_MISSING;
95
0
  else if(nameindex >= amount)
96
0
    return CURLHE_BADINDEX;
97
98
0
  if(nameindex == amount - 1)
99
    /* if the last or only occurrence is what's asked for, then we know it */
100
0
    hs = pick;
101
0
  else {
102
0
    for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) {
103
0
      hs = Curl_node_elem(e);
104
0
      if(curl_strequal(hs->name, name) &&
105
0
         (hs->type & type) &&
106
0
         (hs->request == request) &&
107
0
         (match++ == nameindex)) {
108
0
        e_pick = e;
109
0
        break;
110
0
      }
111
0
    }
112
0
    if(!e) /* this should not happen */
113
0
      return CURLHE_MISSING;
114
0
  }
115
  /* this is the name we want */
116
0
  copy_header_external(hs, nameindex, amount, e_pick,
117
0
                       &data->state.headerout[0]);
118
0
  *hout = &data->state.headerout[0];
119
0
  return CURLHE_OK;
120
0
}
121
122
/* public API */
123
struct curl_header *curl_easy_nextheader(CURL *easy,
124
                                         unsigned int type,
125
                                         int request,
126
                                         struct curl_header *prev)
127
0
{
128
0
  struct Curl_easy *data = easy;
129
0
  struct Curl_llist_node *pick;
130
0
  struct Curl_llist_node *e;
131
0
  struct Curl_header_store *hs;
132
0
  size_t amount = 0;
133
0
  size_t index = 0;
134
135
0
  if(request > data->state.requests)
136
0
    return NULL;
137
0
  if(request == -1)
138
0
    request = data->state.requests;
139
140
0
  if(prev) {
141
0
    pick = prev->anchor;
142
0
    if(!pick)
143
      /* something is wrong */
144
0
      return NULL;
145
0
    pick = Curl_node_next(pick);
146
0
  }
147
0
  else
148
0
    pick = Curl_llist_head(&data->state.httphdrs);
149
150
0
  if(pick) {
151
    /* make sure it is the next header of the desired type */
152
0
    do {
153
0
      hs = Curl_node_elem(pick);
154
0
      if((hs->type & type) && (hs->request == request))
155
0
        break;
156
0
      pick = Curl_node_next(pick);
157
0
    } while(pick);
158
0
  }
159
160
0
  if(!pick)
161
    /* no more headers available */
162
0
    return NULL;
163
164
0
  hs = Curl_node_elem(pick);
165
166
  /* count number of occurrences of this name within the mask and figure out
167
     the index for the currently selected entry */
168
0
  for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) {
169
0
    struct Curl_header_store *check = Curl_node_elem(e);
170
0
    if(curl_strequal(hs->name, check->name) &&
171
0
       (check->request == request) &&
172
0
       (check->type & type))
173
0
      amount++;
174
0
    if(e == pick)
175
0
      index = amount - 1;
176
0
  }
177
178
0
  copy_header_external(hs, index, amount, pick,
179
0
                       &data->state.headerout[1]);
180
0
  return &data->state.headerout[1];
181
0
}
182
183
static CURLcode namevalue(char *header, size_t hlen, unsigned int type,
184
                          char **name, char **value)
185
262k
{
186
262k
  char *end = header + hlen - 1; /* point to the last byte */
187
262k
  DEBUGASSERT(hlen);
188
262k
  *name = header;
189
190
262k
  if(type == CURLH_PSEUDO) {
191
4.63k
    if(*header != ':')
192
0
      return CURLE_BAD_FUNCTION_ARGUMENT;
193
4.63k
    header++;
194
4.63k
  }
195
196
  /* Find the end of the header name */
197
3.10M
  while(*header && (*header != ':'))
198
2.84M
    ++header;
199
200
262k
  if(*header)
201
    /* Skip over colon, null it */
202
262k
    *header++ = 0;
203
178
  else
204
178
    return CURLE_BAD_FUNCTION_ARGUMENT;
205
206
  /* skip all leading blank letters */
207
455k
  while(ISBLANK(*header))
208
192k
    header++;
209
210
262k
  *value = header;
211
212
  /* skip all trailing space letters */
213
270k
  while((end > header) && ISBLANK(*end))
214
8.13k
    *end-- = 0; /* null-terminate */
215
262k
  return CURLE_OK;
216
262k
}
217
218
static CURLcode unfold_value(struct Curl_easy *data, const char *value,
219
                             size_t vlen)  /* length of the incoming header */
220
12.6k
{
221
12.6k
  struct Curl_header_store *hs;
222
12.6k
  struct Curl_header_store *newhs;
223
12.6k
  size_t olen; /* length of the old value */
224
12.6k
  size_t oalloc; /* length of the old name + value + separator */
225
12.6k
  size_t offset;
226
12.6k
  DEBUGASSERT(data->state.prevhead);
227
12.6k
  hs = data->state.prevhead;
228
12.6k
  olen = strlen(hs->value);
229
12.6k
  offset = hs->value - hs->buffer;
230
12.6k
  oalloc = olen + offset + 1;
231
232
  /* skip all trailing space letters */
233
31.7k
  while(vlen && ISBLANK(value[vlen - 1]))
234
19.1k
    vlen--;
235
236
  /* save only one leading space */
237
21.6k
  while((vlen > 1) && ISBLANK(value[0]) && ISBLANK(value[1])) {
238
9.04k
    vlen--;
239
9.04k
    value++;
240
9.04k
  }
241
242
  /* since this header block might move in the realloc below, it needs to
243
     first be unlinked from the list and then re-added again after the
244
     realloc */
245
12.6k
  Curl_node_remove(&hs->node);
246
247
  /* new size = struct + new value length + old name+value length */
248
12.6k
  newhs = Curl_saferealloc(hs, sizeof(*hs) + vlen + oalloc + 1);
249
12.6k
  if(!newhs)
250
0
    return CURLE_OUT_OF_MEMORY;
251
  /* ->name and ->value point into ->buffer (to keep the header allocation
252
     in a single memory block), which now potentially have moved. Adjust
253
     them. */
254
12.6k
  newhs->name = newhs->buffer;
255
12.6k
  newhs->value = &newhs->buffer[offset];
256
257
  /* put the data at the end of the previous data, not the newline */
258
12.6k
  memcpy(&newhs->value[olen], value, vlen);
259
12.6k
  newhs->value[olen + vlen] = 0; /* null-terminate at newline */
260
261
  /* insert this node into the list of headers */
262
12.6k
  Curl_llist_append(&data->state.httphdrs, newhs, &newhs->node);
263
12.6k
  data->state.prevhead = newhs;
264
12.6k
  return CURLE_OK;
265
12.6k
}
266
267
268
/*
269
 * Curl_headers_push() gets passed a full HTTP header to store. It gets called
270
 * immediately before the header callback. The header is CRLF terminated.
271
 */
272
CURLcode Curl_headers_push(struct Curl_easy *data, const char *header,
273
                           unsigned char type)
274
294k
{
275
294k
  char *value = NULL;
276
294k
  char *name = NULL;
277
294k
  char *end;
278
294k
  size_t hlen; /* length of the incoming header */
279
294k
  struct Curl_header_store *hs;
280
294k
  CURLcode result = CURLE_OUT_OF_MEMORY;
281
282
294k
  if((header[0] == '\r') || (header[0] == '\n'))
283
    /* ignore the body separator */
284
18.8k
    return CURLE_OK;
285
286
275k
  end = strchr(header, '\r');
287
275k
  if(!end) {
288
91.9k
    end = strchr(header, '\n');
289
91.9k
    if(!end)
290
      /* neither CR nor LF as terminator is not a valid header */
291
144
      return CURLE_WEIRD_SERVER_REPLY;
292
91.9k
  }
293
275k
  hlen = end - header;
294
295
275k
  if((header[0] == ' ') || (header[0] == '\t')) {
296
12.8k
    if(data->state.prevhead)
297
      /* line folding, append value to the previous header's value */
298
12.6k
      return unfold_value(data, header, hlen);
299
209
    else {
300
      /* cannot unfold without a previous header. Instead of erroring, just
301
         pass the leading blanks. */
302
2.30k
      while(hlen && ISBLANK(*header)) {
303
2.09k
        header++;
304
2.09k
        hlen--;
305
2.09k
      }
306
209
      if(!hlen)
307
16
        return CURLE_WEIRD_SERVER_REPLY;
308
209
    }
309
12.8k
  }
310
262k
  if(Curl_llist_count(&data->state.httphdrs) >= MAX_HTTP_RESP_HEADER_COUNT) {
311
3
    failf(data, "Too many response headers, %d is max",
312
3
          MAX_HTTP_RESP_HEADER_COUNT);
313
3
    return CURLE_TOO_LARGE;
314
3
  }
315
316
262k
  hs = curlx_calloc(1, sizeof(*hs) + hlen);
317
262k
  if(!hs)
318
0
    return CURLE_OUT_OF_MEMORY;
319
262k
  memcpy(hs->buffer, header, hlen);
320
262k
  hs->buffer[hlen] = 0; /* null-terminate */
321
322
262k
  result = namevalue(hs->buffer, hlen, type, &name, &value);
323
262k
  if(!result) {
324
262k
    hs->name = name;
325
262k
    hs->value = value;
326
262k
    hs->type = type;
327
262k
    hs->request = data->state.requests;
328
329
    /* insert this node into the list of headers */
330
262k
    Curl_llist_append(&data->state.httphdrs, hs, &hs->node);
331
262k
    data->state.prevhead = hs;
332
262k
  }
333
178
  else {
334
178
    failf(data, "Invalid response header");
335
178
    curlx_free(hs);
336
178
  }
337
262k
  return result;
338
262k
}
339
340
/*
341
 * Curl_headers_reset(). Reset the headers subsystem.
342
 */
343
static void headers_reset(struct Curl_easy *data)
344
483k
{
345
483k
  Curl_llist_init(&data->state.httphdrs, NULL);
346
483k
  data->state.prevhead = NULL;
347
483k
}
348
349
struct hds_cw_collect_ctx {
350
  struct Curl_cwriter super;
351
};
352
353
static CURLcode hds_cw_collect_write(struct Curl_easy *data,
354
                                     struct Curl_cwriter *writer, int type,
355
                                     const char *buf, size_t blen)
356
323k
{
357
323k
  if((type & CLIENTWRITE_HEADER) && !(type & CLIENTWRITE_STATUS)) {
358
289k
    unsigned char htype = (unsigned char)
359
289k
      (type & CLIENTWRITE_CONNECT ? CURLH_CONNECT :
360
289k
       (type & CLIENTWRITE_1XX ? CURLH_1XX :
361
236k
        (type & CLIENTWRITE_TRAILER ? CURLH_TRAILER :
362
229k
         CURLH_HEADER)));
363
289k
    CURLcode result = Curl_headers_push(data, buf, htype);
364
289k
    CURL_TRC_WRITE(data, "header_collect pushed(type=%x, len=%zu) -> %d",
365
289k
                   htype, blen, result);
366
289k
    if(result)
367
341
      return result;
368
289k
  }
369
322k
  return Curl_cwriter_write(data, writer->next, type, buf, blen);
370
323k
}
371
372
static const struct Curl_cwtype hds_cw_collect = {
373
  "hds-collect",
374
  NULL,
375
  Curl_cwriter_def_init,
376
  hds_cw_collect_write,
377
  Curl_cwriter_def_close,
378
  sizeof(struct hds_cw_collect_ctx)
379
};
380
381
CURLcode Curl_headers_init(struct Curl_easy *data)
382
139k
{
383
139k
  struct Curl_cwriter *writer;
384
139k
  CURLcode result;
385
386
139k
  if(data->conn && (data->conn->handler->protocol & PROTO_FAMILY_HTTP)) {
387
    /* avoid installing it twice */
388
83.2k
    if(Curl_cwriter_get_by_name(data, hds_cw_collect.name))
389
24.6k
      return CURLE_OK;
390
391
58.6k
    result = Curl_cwriter_create(&writer, data, &hds_cw_collect,
392
58.6k
                                 CURL_CW_PROTOCOL);
393
58.6k
    if(result)
394
0
      return result;
395
396
58.6k
    result = Curl_cwriter_add(data, writer);
397
58.6k
    if(result) {
398
0
      Curl_cwriter_free(data, writer);
399
0
      return result;
400
0
    }
401
58.6k
  }
402
114k
  return CURLE_OK;
403
139k
}
404
405
/*
406
 * Curl_headers_cleanup(). Free all stored headers and associated memory.
407
 */
408
CURLcode Curl_headers_cleanup(struct Curl_easy *data)
409
483k
{
410
483k
  struct Curl_llist_node *e;
411
483k
  struct Curl_llist_node *n;
412
413
745k
  for(e = Curl_llist_head(&data->state.httphdrs); e; e = n) {
414
262k
    struct Curl_header_store *hs = Curl_node_elem(e);
415
262k
    n = Curl_node_next(e);
416
262k
    curlx_free(hs);
417
262k
  }
418
483k
  headers_reset(data);
419
483k
  return CURLE_OK;
420
483k
}
421
422
#else /* HTTP-disabled builds below */
423
424
CURLHcode curl_easy_header(CURL *easy,
425
                           const char *name,
426
                           size_t index,
427
                           unsigned int origin,
428
                           int request,
429
                           struct curl_header **hout)
430
{
431
  (void)easy;
432
  (void)name;
433
  (void)index;
434
  (void)origin;
435
  (void)request;
436
  (void)hout;
437
  return CURLHE_NOT_BUILT_IN;
438
}
439
440
struct curl_header *curl_easy_nextheader(CURL *easy,
441
                                         unsigned int type,
442
                                         int request,
443
                                         struct curl_header *prev)
444
{
445
  (void)easy;
446
  (void)type;
447
  (void)request;
448
  (void)prev;
449
  return NULL;
450
}
451
#endif