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