Coverage Report

Created: 2025-10-10 06:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PROJ/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
0
#define MAX_ALTSVC_LINE 4095
50
0
#define MAX_ALTSVC_DATELEN 256
51
0
#define MAX_ALTSVC_HOSTLEN 2048
52
0
#define MAX_ALTSVC_ALPNLEN 10
53
54
0
#define H3VERSION "h3"
55
56
/* Given the ALPN ID, return the name */
57
const char *Curl_alpnid2str(enum alpnid id)
58
0
{
59
0
  switch(id) {
60
0
  case ALPN_h1:
61
0
    return "h1";
62
0
  case ALPN_h2:
63
0
    return "h2";
64
0
  case ALPN_h3:
65
0
    return H3VERSION;
66
0
  default:
67
0
    return ""; /* bad */
68
0
  }
69
0
}
70
71
72
static void altsvc_free(struct altsvc *as)
73
0
{
74
0
  free(as->src.host);
75
0
  free(as->dst.host);
76
0
  free(as);
77
0
}
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
0
{
88
0
  struct altsvc *as = calloc(1, sizeof(struct altsvc));
89
0
  if(!as)
90
0
    return NULL;
91
0
  DEBUGASSERT(hlen);
92
0
  DEBUGASSERT(dlen);
93
0
  if(!hlen || !dlen)
94
    /* bad input */
95
0
    goto error;
96
0
  if((hlen > 2) && srchost[0] == '[') {
97
    /* IPv6 address, strip off brackets */
98
0
    srchost++;
99
0
    hlen -= 2;
100
0
  }
101
0
  else if(srchost[hlen - 1] == '.') {
102
    /* strip off trailing dot */
103
0
    hlen--;
104
0
    if(!hlen)
105
0
      goto error;
106
0
  }
107
0
  if((dlen > 2) && dsthost[0] == '[') {
108
    /* IPv6 address, strip off brackets */
109
0
    dsthost++;
110
0
    dlen -= 2;
111
0
  }
112
113
0
  as->src.host = Curl_memdup0(srchost, hlen);
114
0
  if(!as->src.host)
115
0
    goto error;
116
117
0
  as->dst.host = Curl_memdup0(dsthost, dlen);
118
0
  if(!as->dst.host)
119
0
    goto error;
120
121
0
  as->src.alpnid = srcalpnid;
122
0
  as->dst.alpnid = dstalpnid;
123
0
  as->src.port = (unsigned short)srcport;
124
0
  as->dst.port = (unsigned short)dstport;
125
126
0
  return as;
127
0
error:
128
0
  altsvc_free(as);
129
0
  return NULL;
130
0
}
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
0
{
219
0
  CURLcode result = CURLE_OK;
220
0
  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
0
  free(asi->filename);
225
0
  asi->filename = strdup(file);
226
0
  if(!asi->filename)
227
0
    return CURLE_OUT_OF_MEMORY;
228
229
0
  fp = curlx_fopen(file, FOPEN_READTEXT);
230
0
  if(fp) {
231
0
    struct dynbuf buf;
232
0
    curlx_dyn_init(&buf, MAX_ALTSVC_LINE);
233
0
    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
0
    curlx_dyn_free(&buf); /* free the line buffer */
240
0
    curlx_fclose(fp);
241
0
  }
242
0
  return result;
243
0
}
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
0
{
251
0
  struct tm stamp;
252
0
  const char *dst6_pre = "";
253
0
  const char *dst6_post = "";
254
0
  const char *src6_pre = "";
255
0
  const char *src6_post = "";
256
0
  CURLcode result = Curl_gmtime(as->expires, &stamp);
257
0
  if(result)
258
0
    return result;
259
0
#ifdef USE_IPV6
260
0
  else {
261
0
    char ipv6_unused[16];
262
0
    if(curlx_inet_pton(AF_INET6, as->dst.host, ipv6_unused) == 1) {
263
0
      dst6_pre = "[";
264
0
      dst6_post = "]";
265
0
    }
266
0
    if(curlx_inet_pton(AF_INET6, as->src.host, ipv6_unused) == 1) {
267
0
      src6_pre = "[";
268
0
      src6_post = "]";
269
0
    }
270
0
  }
271
0
#endif
272
0
  curl_mfprintf(fp,
273
0
                "%s %s%s%s %u "
274
0
                "%s %s%s%s %u "
275
0
                "\"%d%02d%02d "
276
0
                "%02d:%02d:%02d\" "
277
0
                "%u %u\n",
278
0
                Curl_alpnid2str(as->src.alpnid),
279
0
                src6_pre, as->src.host, src6_post,
280
0
                as->src.port,
281
282
0
                Curl_alpnid2str(as->dst.alpnid),
283
0
                dst6_pre, as->dst.host, dst6_post,
284
0
                as->dst.port,
285
286
0
                stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday,
287
0
                stamp.tm_hour, stamp.tm_min, stamp.tm_sec,
288
0
                as->persist, as->prio);
289
0
  return CURLE_OK;
290
0
}
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
0
{
300
0
  struct altsvcinfo *asi = calloc(1, sizeof(struct altsvcinfo));
301
0
  if(!asi)
302
0
    return NULL;
303
0
  Curl_llist_init(&asi->list, NULL);
304
305
  /* set default behavior */
306
0
  asi->flags = CURLALTSVC_H1
307
#ifdef USE_HTTP2
308
    | CURLALTSVC_H2
309
#endif
310
#ifdef USE_HTTP3
311
    | CURLALTSVC_H3
312
#endif
313
0
    ;
314
0
  return asi;
315
0
}
316
317
/*
318
 * Curl_altsvc_load() loads alt-svc from file.
319
 */
320
CURLcode Curl_altsvc_load(struct altsvcinfo *asi, const char *file)
321
0
{
322
0
  DEBUGASSERT(asi);
323
0
  return altsvc_load(asi, file);
324
0
}
325
326
/*
327
 * Curl_altsvc_ctrl() passes on the external bitmask.
328
 */
329
CURLcode Curl_altsvc_ctrl(struct altsvcinfo *asi, const long ctrl)
330
0
{
331
0
  DEBUGASSERT(asi);
332
0
  asi->flags = ctrl;
333
0
  return CURLE_OK;
334
0
}
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
0
{
342
0
  if(*altsvcp) {
343
0
    struct Curl_llist_node *e;
344
0
    struct Curl_llist_node *n;
345
0
    struct altsvcinfo *altsvc = *altsvcp;
346
0
    for(e = Curl_llist_head(&altsvc->list); e; e = n) {
347
0
      struct altsvc *as = Curl_node_elem(e);
348
0
      n = Curl_node_next(e);
349
0
      altsvc_free(as);
350
0
    }
351
0
    free(altsvc->filename);
352
0
    free(altsvc);
353
0
    *altsvcp = NULL; /* clear the pointer */
354
0
  }
355
0
}
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
0
{
363
0
  CURLcode result = CURLE_OK;
364
0
  FILE *out;
365
0
  char *tempstore = NULL;
366
367
0
  if(!altsvc)
368
    /* no cache activated */
369
0
    return CURLE_OK;
370
371
  /* if not new name is given, use the one we stored from the load */
372
0
  if(!file && altsvc->filename)
373
0
    file = altsvc->filename;
374
375
0
  if((altsvc->flags & CURLALTSVC_READONLYFILE) || !file || !file[0])
376
    /* marked as read-only, no file or zero length filename */
377
0
    return CURLE_OK;
378
379
0
  result = Curl_fopen(data, file, &out, &tempstore);
380
0
  if(!result) {
381
0
    struct Curl_llist_node *e;
382
0
    struct Curl_llist_node *n;
383
0
    fputs("# Your alt-svc cache. https://curl.se/docs/alt-svc.html\n"
384
0
          "# This file was generated by libcurl! Edit at your own risk.\n",
385
0
          out);
386
0
    for(e = Curl_llist_head(&altsvc->list); e; e = n) {
387
0
      struct altsvc *as = Curl_node_elem(e);
388
0
      n = Curl_node_next(e);
389
0
      result = altsvc_out(as, out);
390
0
      if(result)
391
0
        break;
392
0
    }
393
0
    curlx_fclose(out);
394
0
    if(!result && tempstore && Curl_rename(tempstore, file))
395
0
      result = CURLE_WRITE_ERROR;
396
397
0
    if(result && tempstore)
398
0
      unlink(tempstore);
399
0
  }
400
0
  free(tempstore);
401
0
  return result;
402
0
}
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
0
{
409
0
  size_t hlen = strlen(host);
410
0
  size_t clen = strlen(check);
411
412
0
  if(hlen && (host[hlen - 1] == '.'))
413
0
    hlen--;
414
0
  if(hlen != clen)
415
    /* they cannot match if they have different lengths */
416
0
    return FALSE;
417
0
  return curl_strnequal(host, check, hlen);
418
0
}
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
0
{
425
0
  struct Curl_llist_node *e;
426
0
  struct Curl_llist_node *n;
427
0
  for(e = Curl_llist_head(&asi->list); e; e = n) {
428
0
    struct altsvc *as = Curl_node_elem(e);
429
0
    n = Curl_node_next(e);
430
0
    if((srcalpnid == as->src.alpnid) &&
431
0
       (srcport == as->src.port) &&
432
0
       hostcompare(srchost, as->src.host)) {
433
0
      Curl_node_remove(e);
434
0
      altsvc_free(as);
435
0
    }
436
0
  }
437
0
}
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
{
444
  const char *timestr = getenv("CURL_TIME");
445
  (void)unused;
446
  if(timestr) {
447
    curl_off_t val;
448
    curlx_str_number(&timestr, &val, TIME_T_MAX);
449
    return (time_t)val;
450
  }
451
  return time(NULL);
452
}
453
#undef time
454
#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
0
{
473
0
  const char *p = value;
474
0
  struct altsvc *as;
475
0
  unsigned short dstport = srcport; /* the same by default */
476
0
  size_t entries = 0;
477
0
  struct Curl_str alpn;
478
0
  const char *sp;
479
0
  time_t maxage = 24 * 3600; /* default is 24 hours */
480
0
  bool persist = FALSE;
481
#ifdef CURL_DISABLE_VERBOSE_STRINGS
482
  (void)data;
483
#endif
484
485
0
  DEBUGASSERT(asi);
486
487
  /* initial check for "clear" */
488
0
  if(!curlx_str_cspn(&p, &alpn, ";\n\r")) {
489
0
    curlx_str_trimblanks(&alpn);
490
    /* "clear" is a magic keyword */
491
0
    if(curlx_str_casecompare(&alpn, "clear")) {
492
      /* Flush cached alternatives for this source origin */
493
0
      altsvc_flush(asi, srcalpnid, srchost, srcport);
494
0
      return CURLE_OK;
495
0
    }
496
0
  }
497
498
0
  p = value;
499
500
0
  if(curlx_str_until(&p, &alpn, MAX_ALTSVC_LINE, '='))
501
0
    return CURLE_OK; /* strange line */
502
503
0
  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
0
  sp = strchr(p, ';');
508
0
  if(sp) {
509
0
    sp++; /* pass the semicolon */
510
0
    for(;;) {
511
0
      struct Curl_str name;
512
0
      struct Curl_str val;
513
0
      const char *vp;
514
0
      curl_off_t num;
515
0
      bool quoted;
516
      /* allow some extra whitespaces around name and value */
517
0
      if(curlx_str_until(&sp, &name, 20, '=') ||
518
0
         curlx_str_single(&sp, '=') ||
519
0
         curlx_str_until(&sp, &val, 80, ';'))
520
0
        break;
521
0
      curlx_str_trimblanks(&name);
522
0
      curlx_str_trimblanks(&val);
523
      /* the value might be quoted */
524
0
      vp = curlx_str(&val);
525
0
      quoted = (*vp == '\"');
526
0
      if(quoted)
527
0
        vp++;
528
0
      if(!curlx_str_number(&vp, &num, TIME_T_MAX)) {
529
0
        if(curlx_str_casecompare(&name, "ma"))
530
0
          maxage = (time_t)num;
531
0
        else if(curlx_str_casecompare(&name, "persist") && (num == 1))
532
0
          persist = TRUE;
533
0
      }
534
0
      if(quoted && curlx_str_single(&sp, '\"'))
535
0
        break;
536
0
      if(curlx_str_single(&sp, ';'))
537
0
        break;
538
0
    }
539
0
  }
540
541
0
  do {
542
0
    if(!curlx_str_single(&p, '=')) {
543
      /* [protocol]="[host][:port], [protocol]="[host][:port]" */
544
0
      enum alpnid dstalpnid =
545
0
        Curl_alpn2alpnid(curlx_str(&alpn), curlx_strlen(&alpn));
546
0
      if(!curlx_str_single(&p, '\"')) {
547
0
        struct Curl_str dsthost;
548
0
        curl_off_t port = 0;
549
0
        if(curlx_str_single(&p, ':')) {
550
          /* hostname starts here */
551
0
          if(curlx_str_single(&p, '[')) {
552
0
            if(curlx_str_until(&p, &dsthost, MAX_ALTSVC_HOSTLEN, ':')) {
553
0
              infof(data, "Bad alt-svc hostname, ignoring.");
554
0
              break;
555
0
            }
556
0
          }
557
0
          else {
558
            /* IPv6 host name */
559
0
            if(curlx_str_until(&p, &dsthost, MAX_IPADR_LEN, ']') ||
560
0
               curlx_str_single(&p, ']')) {
561
0
              infof(data, "Bad alt-svc IPv6 hostname, ignoring.");
562
0
              break;
563
0
            }
564
0
          }
565
0
          if(curlx_str_single(&p, ':'))
566
0
            break;
567
0
        }
568
0
        else
569
          /* no destination name, use source host */
570
0
          curlx_str_assign(&dsthost, srchost, strlen(srchost));
571
572
0
        if(curlx_str_number(&p, &port, 0xffff)) {
573
0
          infof(data, "Unknown alt-svc port number, ignoring.");
574
0
          break;
575
0
        }
576
577
0
        dstport = (unsigned short)port;
578
579
0
        if(curlx_str_single(&p, '\"'))
580
0
          break;
581
582
0
        if(dstalpnid) {
583
0
          if(!entries++)
584
            /* Flush cached alternatives for this source origin, if any - when
585
               this is the first entry of the line. */
586
0
            altsvc_flush(asi, srcalpnid, srchost, srcport);
587
588
0
          as = altsvc_createid(srchost, strlen(srchost),
589
0
                               curlx_str(&dsthost),
590
0
                               curlx_strlen(&dsthost),
591
0
                               srcalpnid, dstalpnid,
592
0
                               srcport, dstport);
593
0
          if(as) {
594
0
            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
0
            if(maxage > (TIME_T_MAX - secs))
598
0
              as->expires = TIME_T_MAX;
599
0
            else
600
0
              as->expires = maxage + secs;
601
0
            as->persist = persist;
602
0
            Curl_llist_append(&asi->list, as, &as->node);
603
0
            infof(data, "Added alt-svc: %.*s:%d over %s",
604
0
                  (int)curlx_strlen(&dsthost), curlx_str(&dsthost),
605
0
                  dstport, Curl_alpnid2str(dstalpnid));
606
0
          }
607
0
        }
608
0
      }
609
0
      else
610
0
        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
0
      if(curlx_str_single(&p, ','))
615
0
        break;
616
617
      /* comma means another alternative is present */
618
0
      if(curlx_str_until(&p, &alpn, MAX_ALTSVC_LINE, '='))
619
0
        break;
620
0
      curlx_str_trimblanks(&alpn);
621
0
    }
622
0
    else
623
0
      break;
624
0
  } while(1);
625
626
0
  return CURLE_OK;
627
0
}
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 */