Coverage Report

Created: 2025-07-11 07:03

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