Coverage Report

Created: 2026-03-07 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/altsvc.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
 * The Alt-Svc: header is defined in RFC 7838:
26
 * https://datatracker.ietf.org/doc/html/rfc7838
27
 */
28
#include "curl_setup.h"
29
30
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_ALTSVC)
31
#include "urldata.h"
32
#include "altsvc.h"
33
#include "curl_fopen.h"
34
#include "curl_get_line.h"
35
#include "parsedate.h"
36
#include "curl_trc.h"
37
#include "curlx/inet_pton.h"
38
#include "curlx/strparse.h"
39
#include "connect.h"
40
41
167k
#define MAX_ALTSVC_LINE    4095
42
0
#define MAX_ALTSVC_DATELEN 17
43
2.35k
#define MAX_ALTSVC_HOSTLEN 2048
44
161k
#define MAX_ALTSVC_ALPNLEN 10
45
46
178
#define H3VERSION "h3"
47
48
/* Given the ALPN ID, return the name */
49
const char *Curl_alpnid2str(enum alpnid id)
50
3.11k
{
51
3.11k
  switch(id) {
52
2.68k
  case ALPN_h1:
53
2.68k
    return "h1";
54
246
  case ALPN_h2:
55
246
    return "h2";
56
178
  case ALPN_h3:
57
178
    return H3VERSION;
58
0
  default:
59
0
    return ""; /* bad */
60
3.11k
  }
61
3.11k
}
62
63
2.18k
#define altsvc_free(x) curlx_free(x)
64
65
static struct altsvc *altsvc_createid(const char *srchost,
66
                                      size_t hlen,
67
                                      const char *dsthost,
68
                                      size_t dlen, /* dsthost length */
69
                                      enum alpnid srcalpnid,
70
                                      enum alpnid dstalpnid,
71
                                      size_t srcport,
72
                                      size_t dstport)
73
2.18k
{
74
2.18k
  struct altsvc *as;
75
2.18k
  if((hlen > 2) && srchost[0] == '[') {
76
    /* IPv6 address, strip off brackets */
77
0
    srchost++;
78
0
    hlen -= 2;
79
0
  }
80
2.18k
  else if(hlen && (srchost[hlen - 1] == '.')) {
81
    /* strip off trailing dot */
82
260
    hlen--;
83
260
  }
84
2.18k
  if((dlen > 2) && dsthost[0] == '[') {
85
    /* IPv6 address, strip off brackets */
86
0
    dsthost++;
87
0
    dlen -= 2;
88
0
  }
89
2.18k
  if(!hlen || !dlen)
90
    /* bad input */
91
1
    return NULL;
92
  /* struct size plus both strings */
93
2.18k
  as = curlx_calloc(1, sizeof(struct altsvc) + (hlen + 1) + (dlen + 1));
94
2.18k
  if(!as)
95
0
    return NULL;
96
2.18k
  as->src.host = (char *)as + sizeof(struct altsvc);
97
2.18k
  memcpy(as->src.host, srchost, hlen);
98
  /* the null terminator is already there */
99
100
2.18k
  as->dst.host = (char *)as + sizeof(struct altsvc) + hlen + 1;
101
2.18k
  memcpy(as->dst.host, dsthost, dlen);
102
  /* the null terminator is already there */
103
104
2.18k
  as->src.alpnid = srcalpnid;
105
2.18k
  as->dst.alpnid = dstalpnid;
106
2.18k
  as->src.port = (unsigned short)srcport;
107
2.18k
  as->dst.port = (unsigned short)dstport;
108
109
2.18k
  return as;
110
2.18k
}
111
112
static struct altsvc *altsvc_create(struct Curl_str *srchost,
113
                                    struct Curl_str *dsthost,
114
                                    struct Curl_str *srcalpn,
115
                                    struct Curl_str *dstalpn,
116
                                    size_t srcport,
117
                                    size_t dstport)
118
0
{
119
0
  enum alpnid dstalpnid = Curl_str2alpnid(dstalpn);
120
0
  enum alpnid srcalpnid = Curl_str2alpnid(srcalpn);
121
0
  if(!srcalpnid || !dstalpnid)
122
0
    return NULL;
123
0
  return altsvc_createid(curlx_str(srchost), curlx_strlen(srchost),
124
0
                         curlx_str(dsthost), curlx_strlen(dsthost),
125
0
                         srcalpnid, dstalpnid,
126
0
                         srcport, dstport);
127
0
}
128
129
/* only returns SERIOUS errors */
130
static CURLcode altsvc_add(struct altsvcinfo *asi, const char *line)
131
161k
{
132
  /* Example line:
133
     h2 example.com 443 h3 shiny.example.com 8443 "20191231 10:00:00" 1
134
   */
135
161k
  struct Curl_str srchost;
136
161k
  struct Curl_str dsthost;
137
161k
  struct Curl_str srcalpn;
138
161k
  struct Curl_str dstalpn;
139
161k
  struct Curl_str date;
140
161k
  curl_off_t srcport;
141
161k
  curl_off_t dstport;
142
161k
  curl_off_t persist;
143
161k
  curl_off_t prio;
144
145
161k
  if(curlx_str_word(&line, &srcalpn, MAX_ALTSVC_ALPNLEN) ||
146
161k
     curlx_str_singlespace(&line) ||
147
0
     curlx_str_word(&line, &srchost, MAX_ALTSVC_HOSTLEN) ||
148
0
     curlx_str_singlespace(&line) ||
149
0
     curlx_str_number(&line, &srcport, 65535) ||
150
0
     curlx_str_singlespace(&line) ||
151
0
     curlx_str_word(&line, &dstalpn, MAX_ALTSVC_ALPNLEN) ||
152
0
     curlx_str_singlespace(&line) ||
153
0
     curlx_str_word(&line, &dsthost, MAX_ALTSVC_HOSTLEN) ||
154
0
     curlx_str_singlespace(&line) ||
155
0
     curlx_str_number(&line, &dstport, 65535) ||
156
0
     curlx_str_singlespace(&line) ||
157
0
     curlx_str_quotedword(&line, &date, MAX_ALTSVC_DATELEN) ||
158
0
     curlx_str_singlespace(&line) ||
159
0
     curlx_str_number(&line, &persist, 1) ||
160
0
     curlx_str_singlespace(&line) ||
161
0
     curlx_str_number(&line, &prio, 0) ||
162
0
     curlx_str_newline(&line))
163
161k
    ;
164
0
  else {
165
0
    struct altsvc *as;
166
0
    char dbuf[MAX_ALTSVC_DATELEN + 1];
167
0
    time_t expires = 0;
168
169
    /* The date parser works on a null-terminated string. The maximum length
170
       is upheld by curlx_str_quotedword(). */
171
0
    memcpy(dbuf, curlx_str(&date), curlx_strlen(&date));
172
0
    dbuf[curlx_strlen(&date)] = 0;
173
0
    Curl_getdate_capped(dbuf, &expires);
174
0
    as = altsvc_create(&srchost, &dsthost, &srcalpn, &dstalpn,
175
0
                       (size_t)srcport, (size_t)dstport);
176
0
    if(as) {
177
0
      as->expires = expires;
178
0
      as->prio = 0; /* not supported, set zero */
179
0
      as->persist = persist ? 1 : 0;
180
0
      Curl_llist_append(&asi->list, as, &as->node);
181
0
    }
182
0
    else
183
0
      return CURLE_OUT_OF_MEMORY;
184
0
  }
185
186
161k
  return CURLE_OK;
187
161k
}
188
189
/*
190
 * Load alt-svc entries from the given file. The text based line-oriented file
191
 * format is documented here: https://curl.se/docs/alt-svc.html
192
 *
193
 * This function only returns error on major problems that prevent alt-svc
194
 * handling to work completely. It will ignore individual syntactical errors
195
 * etc.
196
 */
197
static CURLcode altsvc_load(struct altsvcinfo *asi, const char *file)
198
161k
{
199
161k
  CURLcode result = CURLE_OK;
200
161k
  FILE *fp;
201
202
  /* we need a private copy of the filename so that the altsvc cache file
203
     name survives an easy handle reset */
204
161k
  curlx_free(asi->filename);
205
161k
  asi->filename = curlx_strdup(file);
206
161k
  if(!asi->filename)
207
0
    return CURLE_OUT_OF_MEMORY;
208
209
161k
  fp = curlx_fopen(file, FOPEN_READTEXT);
210
161k
  if(fp) {
211
161k
    bool eof = FALSE;
212
161k
    struct dynbuf buf;
213
161k
    curlx_dyn_init(&buf, MAX_ALTSVC_LINE);
214
161k
    do {
215
161k
      result = Curl_get_line(&buf, fp, &eof);
216
161k
      if(!result) {
217
161k
        const char *lineptr = curlx_dyn_ptr(&buf);
218
161k
        curlx_str_passblanks(&lineptr);
219
161k
        if(curlx_str_single(&lineptr, '#'))
220
161k
          altsvc_add(asi, lineptr);
221
161k
      }
222
161k
    } while(!result && !eof);
223
161k
    curlx_dyn_free(&buf); /* free the line buffer */
224
161k
    curlx_fclose(fp);
225
161k
  }
226
161k
  return result;
227
161k
}
228
229
/*
230
 * Write this single altsvc entry to a single output line
231
 */
232
static CURLcode altsvc_out(struct altsvc *as, FILE *fp)
233
1.55k
{
234
1.55k
  struct tm stamp;
235
1.55k
  const char *dst6_pre = "";
236
1.55k
  const char *dst6_post = "";
237
1.55k
  const char *src6_pre = "";
238
1.55k
  const char *src6_post = "";
239
1.55k
  CURLcode result = curlx_gmtime(as->expires, &stamp);
240
1.55k
  if(result)
241
3
    return result;
242
1.55k
#ifdef USE_IPV6
243
1.55k
  else {
244
1.55k
    char ipv6_unused[16];
245
1.55k
    if(curlx_inet_pton(AF_INET6, as->dst.host, ipv6_unused) == 1) {
246
221
      dst6_pre = "[";
247
221
      dst6_post = "]";
248
221
    }
249
1.55k
    if(curlx_inet_pton(AF_INET6, as->src.host, ipv6_unused) == 1) {
250
644
      src6_pre = "[";
251
644
      src6_post = "]";
252
644
    }
253
1.55k
  }
254
1.55k
#endif
255
1.55k
  curl_mfprintf(fp,
256
1.55k
                "%s %s%s%s %u "
257
1.55k
                "%s %s%s%s %u "
258
1.55k
                "\"%d%02d%02d "
259
1.55k
                "%02d:%02d:%02d\" "
260
1.55k
                "%u %u\n",
261
1.55k
                Curl_alpnid2str(as->src.alpnid),
262
1.55k
                src6_pre, as->src.host, src6_post,
263
1.55k
                as->src.port,
264
265
1.55k
                Curl_alpnid2str(as->dst.alpnid),
266
1.55k
                dst6_pre, as->dst.host, dst6_post,
267
1.55k
                as->dst.port,
268
269
1.55k
                stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday,
270
1.55k
                stamp.tm_hour, stamp.tm_min, stamp.tm_sec,
271
1.55k
                as->persist, as->prio);
272
1.55k
  return CURLE_OK;
273
1.55k
}
274
275
/* ---- library-wide functions below ---- */
276
277
/*
278
 * Curl_altsvc_init() creates a new altsvc cache.
279
 * It returns the new instance or NULL if something goes wrong.
280
 */
281
struct altsvcinfo *Curl_altsvc_init(void)
282
161k
{
283
161k
  struct altsvcinfo *asi = curlx_calloc(1, sizeof(struct altsvcinfo));
284
161k
  if(!asi)
285
0
    return NULL;
286
161k
  Curl_llist_init(&asi->list, NULL);
287
288
  /* set default behavior */
289
161k
  asi->flags = CURLALTSVC_H1
290
161k
#ifdef USE_HTTP2
291
161k
    | CURLALTSVC_H2
292
161k
#endif
293
#ifdef USE_HTTP3
294
    | CURLALTSVC_H3
295
#endif
296
161k
    ;
297
161k
  return asi;
298
161k
}
299
300
/*
301
 * Curl_altsvc_load() loads alt-svc from file.
302
 */
303
CURLcode Curl_altsvc_load(struct altsvcinfo *asi, const char *file)
304
161k
{
305
161k
  DEBUGASSERT(asi);
306
161k
  return altsvc_load(asi, file);
307
161k
}
308
309
/*
310
 * Curl_altsvc_ctrl() passes on the external bitmask.
311
 */
312
CURLcode Curl_altsvc_ctrl(struct Curl_easy *data, const long ctrl)
313
293
{
314
293
  DEBUGASSERT(data);
315
293
  if(!ctrl)
316
16
    return CURLE_BAD_FUNCTION_ARGUMENT;
317
318
277
  if(!data->asi) {
319
277
    data->asi = Curl_altsvc_init();
320
277
    if(!data->asi)
321
0
      return CURLE_OUT_OF_MEMORY;
322
277
  }
323
277
  data->asi->flags = ctrl;
324
277
  return CURLE_OK;
325
277
}
326
327
/*
328
 * Curl_altsvc_cleanup() frees an altsvc cache instance and all associated
329
 * resources.
330
 */
331
void Curl_altsvc_cleanup(struct altsvcinfo **asi)
332
351k
{
333
351k
  if(*asi) {
334
161k
    struct Curl_llist_node *e;
335
161k
    struct Curl_llist_node *n;
336
161k
    struct altsvcinfo *altsvc = *asi;
337
163k
    for(e = Curl_llist_head(&altsvc->list); e; e = n) {
338
1.55k
      struct altsvc *as = Curl_node_elem(e);
339
1.55k
      n = Curl_node_next(e);
340
1.55k
      altsvc_free(as);
341
1.55k
    }
342
161k
    curlx_free(altsvc->filename);
343
161k
    curlx_free(altsvc);
344
161k
    *asi = NULL; /* clear the pointer */
345
161k
  }
346
351k
}
347
348
/*
349
 * Curl_altsvc_save() writes the altsvc cache to a file.
350
 */
351
CURLcode Curl_altsvc_save(struct Curl_easy *data,
352
                          struct altsvcinfo *asi, const char *file)
353
351k
{
354
351k
  CURLcode result = CURLE_OK;
355
351k
  FILE *out;
356
351k
  char *tempstore = NULL;
357
358
351k
  if(!asi)
359
    /* no cache activated */
360
190k
    return CURLE_OK;
361
362
  /* if not new name is given, use the one we stored from the load */
363
161k
  if(!file && asi->filename)
364
0
    file = asi->filename;
365
366
161k
  if((asi->flags & CURLALTSVC_READONLYFILE) || !file || !file[0])
367
    /* marked as read-only, no file or zero length filename */
368
205
    return CURLE_OK;
369
370
161k
  result = Curl_fopen(data, file, &out, &tempstore);
371
161k
  if(!result) {
372
161k
    struct Curl_llist_node *e;
373
161k
    struct Curl_llist_node *n;
374
161k
    fputs("# Your alt-svc cache. https://curl.se/docs/alt-svc.html\n"
375
161k
          "# This file was generated by libcurl! Edit at your own risk.\n",
376
161k
          out);
377
162k
    for(e = Curl_llist_head(&asi->list); e; e = n) {
378
1.55k
      struct altsvc *as = Curl_node_elem(e);
379
1.55k
      n = Curl_node_next(e);
380
1.55k
      result = altsvc_out(as, out);
381
1.55k
      if(result)
382
3
        break;
383
1.55k
    }
384
161k
    curlx_fclose(out);
385
161k
    if(!result && tempstore && curlx_rename(tempstore, file))
386
0
      result = CURLE_WRITE_ERROR;
387
388
161k
    if(result && tempstore)
389
0
      unlink(tempstore);
390
161k
  }
391
161k
  curlx_free(tempstore);
392
161k
  return result;
393
161k
}
394
395
/* hostcompare() returns true if 'host' matches 'check'. The first host
396
 * argument may have a trailing dot present that will be ignored.
397
 */
398
static bool hostcompare(const char *host, const char *check)
399
626
{
400
626
  size_t hlen = strlen(host);
401
626
  size_t clen = strlen(check);
402
403
626
  if(hlen && (host[hlen - 1] == '.'))
404
130
    hlen--;
405
626
  if(hlen != clen)
406
    /* they cannot match if they have different lengths */
407
0
    return FALSE;
408
626
  return curl_strnequal(host, check, hlen);
409
626
}
410
411
/* altsvc_flush() removes all alternatives for this source origin from the
412
   list */
413
static void altsvc_flush(struct altsvcinfo *asi, enum alpnid srcalpnid,
414
                         const char *srchost, unsigned short srcport)
415
355
{
416
355
  struct Curl_llist_node *e;
417
355
  struct Curl_llist_node *n;
418
981
  for(e = Curl_llist_head(&asi->list); e; e = n) {
419
626
    struct altsvc *as = Curl_node_elem(e);
420
626
    n = Curl_node_next(e);
421
626
    if((srcalpnid == as->src.alpnid) &&
422
626
       (srcport == as->src.port) &&
423
626
       hostcompare(srchost, as->src.host)) {
424
626
      Curl_node_remove(e);
425
626
      altsvc_free(as);
426
626
    }
427
626
  }
428
355
}
429
430
#if defined(DEBUGBUILD) || defined(UNITTESTS)
431
/* to play well with debug builds, we can *set* a fixed time this will
432
   return */
433
static time_t altsvc_debugtime(void *unused)
434
2.18k
{
435
2.18k
  const char *timestr = getenv("CURL_TIME");
436
2.18k
  (void)unused;
437
2.18k
  if(timestr) {
438
0
    curl_off_t val;
439
0
    curlx_str_number(&timestr, &val, TIME_T_MAX);
440
0
    return (time_t)val;
441
0
  }
442
2.18k
  return time(NULL);
443
2.18k
}
444
#undef time
445
2.18k
#define time(x) altsvc_debugtime(x)
446
#endif
447
448
/*
449
 * Curl_altsvc_parse() takes an incoming alt-svc response header and stores
450
 * the data correctly in the cache.
451
 *
452
 * 'value' points to the header *value*. That is contents to the right of the
453
 * header name.
454
 *
455
 * Currently this function rejects invalid data without returning an error.
456
 * Invalid hostname, port number will result in the specific alternative
457
 * being rejected. Unknown protocols are skipped.
458
 */
459
CURLcode Curl_altsvc_parse(struct Curl_easy *data,
460
                           struct altsvcinfo *asi, const char *value,
461
                           enum alpnid srcalpnid, const char *srchost,
462
                           unsigned short srcport)
463
1.91k
{
464
1.91k
  const char *p = value;
465
1.91k
  struct altsvc *as;
466
1.91k
  unsigned short dstport = srcport; /* the same by default */
467
1.91k
  size_t entries = 0;
468
1.91k
  struct Curl_str alpn;
469
470
1.91k
  DEBUGASSERT(asi);
471
472
  /* initial check for "clear" */
473
1.91k
  if(!curlx_str_cspn(&p, &alpn, ";\n\r")) {
474
1.81k
    curlx_str_trimblanks(&alpn);
475
    /* "clear" is a magic keyword */
476
1.81k
    if(curlx_str_casecompare(&alpn, "clear")) {
477
      /* Flush cached alternatives for this source origin */
478
51
      altsvc_flush(asi, srcalpnid, srchost, srcport);
479
51
      return CURLE_OK;
480
51
    }
481
1.81k
  }
482
483
1.86k
  p = value;
484
485
1.86k
  if(curlx_str_until(&p, &alpn, MAX_ALTSVC_LINE, '='))
486
91
    return CURLE_OK; /* strange line */
487
488
1.77k
  curlx_str_trimblanks(&alpn);
489
490
5.82k
  do {
491
5.82k
    if(!curlx_str_single(&p, '=')) {
492
5.43k
      time_t maxage = 24 * 3600; /* default is 24 hours */
493
5.43k
      bool persist = FALSE;
494
      /* [protocol]="[host][:port], [protocol]="[host][:port]" */
495
5.43k
      enum alpnid dstalpnid = Curl_str2alpnid(&alpn);
496
5.43k
      if(!curlx_str_single(&p, '\"')) {
497
5.21k
        struct Curl_str dsthost;
498
5.21k
        curl_off_t port = 0;
499
5.21k
        if(curlx_str_single(&p, ':')) {
500
          /* hostname starts here */
501
2.61k
          if(curlx_str_single(&p, '[')) {
502
2.35k
            if(curlx_str_until(&p, &dsthost, MAX_ALTSVC_HOSTLEN, ':')) {
503
1
              infof(data, "Bad alt-svc hostname, ignoring.");
504
1
              break;
505
1
            }
506
2.35k
          }
507
258
          else {
508
            /* IPv6 hostname */
509
258
            if(curlx_str_until(&p, &dsthost, MAX_IPADR_LEN, ']') ||
510
217
               curlx_str_single(&p, ']')) {
511
217
              infof(data, "Bad alt-svc IPv6 hostname, ignoring.");
512
217
              break;
513
217
            }
514
258
          }
515
2.39k
          if(curlx_str_single(&p, ':'))
516
188
            break;
517
2.39k
        }
518
2.60k
        else
519
          /* no destination name, use source host */
520
2.60k
          curlx_str_assign(&dsthost, srchost, strlen(srchost));
521
522
4.81k
        if(curlx_str_number(&p, &port, 0xffff)) {
523
353
          infof(data, "Unknown alt-svc port number, ignoring.");
524
353
          break;
525
353
        }
526
527
4.46k
        dstport = (unsigned short)port;
528
529
4.46k
        if(curlx_str_single(&p, '\"'))
530
95
          break;
531
532
        /* Handle the optional 'ma' and 'persist' flags. Unknown flags are
533
           skipped. */
534
4.36k
        curlx_str_passblanks(&p);
535
4.36k
        if(!curlx_str_single(&p, ';')) {
536
325
          for(;;) {
537
325
            struct Curl_str name;
538
325
            struct Curl_str val;
539
325
            const char *vp;
540
325
            curl_off_t num;
541
325
            bool quoted;
542
            /* allow some extra whitespaces around name and value */
543
325
            if(curlx_str_until(&p, &name, 20, '=') ||
544
280
               curlx_str_single(&p, '=') ||
545
216
               curlx_str_cspn(&p, &val, ",;"))
546
152
              break;
547
173
            curlx_str_trimblanks(&name);
548
173
            curlx_str_trimblanks(&val);
549
            /* the value might be quoted */
550
173
            vp = curlx_str(&val);
551
173
            quoted = (*vp == '\"');
552
173
            if(quoted)
553
83
              vp++;
554
173
            if(!curlx_str_number(&vp, &num, TIME_T_MAX)) {
555
105
              if(curlx_str_casecompare(&name, "ma"))
556
30
                maxage = (time_t)num;
557
75
              else if(curlx_str_casecompare(&name, "persist") && (num == 1))
558
0
                persist = TRUE;
559
105
            }
560
68
            else
561
68
              break;
562
105
            p = vp; /* point to the byte ending the value */
563
105
            curlx_str_passblanks(&p);
564
105
            if(quoted && curlx_str_single(&p, '\"'))
565
24
              break;
566
81
            curlx_str_passblanks(&p);
567
81
            if(curlx_str_single(&p, ';'))
568
37
              break;
569
81
          }
570
281
        }
571
4.36k
        if(dstalpnid) {
572
2.18k
          if(!entries++)
573
            /* Flush cached alternatives for this source origin, if any - when
574
               this is the first entry of the line. */
575
304
            altsvc_flush(asi, srcalpnid, srchost, srcport);
576
577
2.18k
          as = altsvc_createid(srchost, strlen(srchost),
578
2.18k
                               curlx_str(&dsthost),
579
2.18k
                               curlx_strlen(&dsthost),
580
2.18k
                               srcalpnid, dstalpnid,
581
2.18k
                               srcport, dstport);
582
2.18k
          if(as) {
583
2.18k
            time_t secs = time(NULL);
584
            /* The expires time also needs to take the Age: value (if any)
585
               into account. [See RFC 7838 section 3.1] */
586
2.18k
            if(maxage > (TIME_T_MAX - secs))
587
0
              as->expires = TIME_T_MAX;
588
2.18k
            else
589
2.18k
              as->expires = maxage + secs;
590
2.18k
            as->persist = persist;
591
2.18k
            Curl_llist_append(&asi->list, as, &as->node);
592
2.18k
            infof(data, "Added alt-svc: %.*s:%d over %s",
593
2.18k
                  (int)curlx_strlen(&dsthost), curlx_str(&dsthost),
594
2.18k
                  dstport, Curl_alpnid2str(dstalpnid));
595
2.18k
          }
596
1
          else
597
1
            return CURLE_OUT_OF_MEMORY;
598
2.18k
        }
599
4.36k
      }
600
214
      else
601
214
        break;
602
603
      /* after the double quote there can be a comma if there is another
604
         string or a semicolon if no more */
605
4.36k
      if(curlx_str_single(&p, ','))
606
266
        break;
607
608
      /* comma means another alternative is present */
609
4.09k
      if(curlx_str_until(&p, &alpn, MAX_ALTSVC_LINE, '='))
610
47
        break;
611
4.05k
      curlx_str_trimblanks(&alpn);
612
4.05k
    }
613
390
    else
614
390
      break;
615
5.82k
  } while(1);
616
617
1.77k
  return CURLE_OK;
618
1.77k
}
619
620
/*
621
 * Return TRUE on a match
622
 */
623
bool Curl_altsvc_lookup(struct altsvcinfo *asi,
624
                        enum alpnid srcalpnid, const char *srchost,
625
                        int srcport,
626
                        struct altsvc **dstentry,
627
                        const int versions, /* one or more bits */
628
                        bool *psame_destination)
629
0
{
630
0
  struct Curl_llist_node *e;
631
0
  struct Curl_llist_node *n;
632
0
  time_t now = time(NULL);
633
0
  DEBUGASSERT(asi);
634
0
  DEBUGASSERT(srchost);
635
0
  DEBUGASSERT(dstentry);
636
637
0
  *psame_destination = FALSE;
638
0
  for(e = Curl_llist_head(&asi->list); e; e = n) {
639
0
    struct altsvc *as = Curl_node_elem(e);
640
0
    n = Curl_node_next(e);
641
0
    if(as->expires < now) {
642
      /* an expired entry, remove */
643
0
      Curl_node_remove(e);
644
0
      altsvc_free(as);
645
0
      continue;
646
0
    }
647
0
    if((as->src.alpnid == srcalpnid) &&
648
0
       hostcompare(srchost, as->src.host) &&
649
0
       (as->src.port == srcport) &&
650
0
       (versions & (int)as->dst.alpnid)) {
651
      /* match */
652
0
      *dstentry = as;
653
0
      *psame_destination = (srcport == as->dst.port) &&
654
0
                           hostcompare(srchost, as->dst.host);
655
0
      return TRUE;
656
0
    }
657
0
  }
658
0
  return FALSE;
659
0
}
660
661
#if defined(DEBUGBUILD) || defined(UNITTESTS)
662
#undef time
663
#endif
664
665
#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_ALTSVC */