Coverage Report

Created: 2025-11-11 06:28

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