Coverage Report

Created: 2026-05-30 06:25

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