Coverage Report

Created: 2025-10-10 06:19

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