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