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