xref: /curl/lib/altsvc.c (revision 433d7303)
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,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                                       enum alpnid srcalpnid,
98                                       enum alpnid dstalpnid,
99                                       unsigned int srcport,
100                                       unsigned int dstport)
101 {
102   struct altsvc *as = calloc(1, sizeof(struct altsvc));
103   size_t hlen;
104   size_t dlen;
105   if(!as)
106     return NULL;
107   hlen = strlen(srchost);
108   dlen = strlen(dsthost);
109   DEBUGASSERT(hlen);
110   DEBUGASSERT(dlen);
111   if(!hlen || !dlen) {
112     /* bad input */
113     free(as);
114     return NULL;
115   }
116   if((hlen > 2) && srchost[0] == '[') {
117     /* IPv6 address, strip off brackets */
118     srchost++;
119     hlen -= 2;
120   }
121   else if(srchost[hlen - 1] == '.')
122     /* strip off trailing dot */
123     hlen--;
124   if((dlen > 2) && dsthost[0] == '[') {
125     /* IPv6 address, strip off brackets */
126     dsthost++;
127     dlen -= 2;
128   }
129 
130   as->src.host = Curl_memdup0(srchost, hlen);
131   if(!as->src.host)
132     goto error;
133 
134   as->dst.host = Curl_memdup0(dsthost, dlen);
135   if(!as->dst.host)
136     goto error;
137 
138   as->src.alpnid = srcalpnid;
139   as->dst.alpnid = dstalpnid;
140   as->src.port = curlx_ultous(srcport);
141   as->dst.port = curlx_ultous(dstport);
142 
143   return as;
144 error:
145   altsvc_free(as);
146   return NULL;
147 }
148 
altsvc_create(char * srchost,char * dsthost,char * srcalpn,char * dstalpn,unsigned int srcport,unsigned int dstport)149 static struct altsvc *altsvc_create(char *srchost,
150                                     char *dsthost,
151                                     char *srcalpn,
152                                     char *dstalpn,
153                                     unsigned int srcport,
154                                     unsigned int dstport)
155 {
156   enum alpnid dstalpnid = alpn2alpnid(dstalpn);
157   enum alpnid srcalpnid = alpn2alpnid(srcalpn);
158   if(!srcalpnid || !dstalpnid)
159     return NULL;
160   return altsvc_createid(srchost, dsthost, 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   CURLcode result;
321   DEBUGASSERT(asi);
322   result = altsvc_load(asi, file);
323   return result;
324 }
325 
326 /*
327  * Curl_altsvc_ctrl() passes on the external bitmask.
328  */
Curl_altsvc_ctrl(struct altsvcinfo * asi,const long ctrl)329 CURLcode Curl_altsvc_ctrl(struct altsvcinfo *asi, const long ctrl)
330 {
331   DEBUGASSERT(asi);
332   asi->flags = ctrl;
333   return CURLE_OK;
334 }
335 
336 /*
337  * Curl_altsvc_cleanup() frees an altsvc cache instance and all associated
338  * resources.
339  */
Curl_altsvc_cleanup(struct altsvcinfo ** altsvcp)340 void Curl_altsvc_cleanup(struct altsvcinfo **altsvcp)
341 {
342   if(*altsvcp) {
343     struct Curl_llist_node *e;
344     struct Curl_llist_node *n;
345     struct altsvcinfo *altsvc = *altsvcp;
346     for(e = Curl_llist_head(&altsvc->list); e; e = n) {
347       struct altsvc *as = Curl_node_elem(e);
348       n = Curl_node_next(e);
349       altsvc_free(as);
350     }
351     free(altsvc->filename);
352     free(altsvc);
353     *altsvcp = NULL; /* clear the pointer */
354   }
355 }
356 
357 /*
358  * Curl_altsvc_save() writes the altsvc cache to a file.
359  */
Curl_altsvc_save(struct Curl_easy * data,struct altsvcinfo * altsvc,const char * file)360 CURLcode Curl_altsvc_save(struct Curl_easy *data,
361                           struct altsvcinfo *altsvc, const char *file)
362 {
363   CURLcode result = CURLE_OK;
364   FILE *out;
365   char *tempstore = NULL;
366 
367   if(!altsvc)
368     /* no cache activated */
369     return CURLE_OK;
370 
371   /* if not new name is given, use the one we stored from the load */
372   if(!file && altsvc->filename)
373     file = altsvc->filename;
374 
375   if((altsvc->flags & CURLALTSVC_READONLYFILE) || !file || !file[0])
376     /* marked as read-only, no file or zero length filename */
377     return CURLE_OK;
378 
379   result = Curl_fopen(data, file, &out, &tempstore);
380   if(!result) {
381     struct Curl_llist_node *e;
382     struct Curl_llist_node *n;
383     fputs("# Your alt-svc cache. https://curl.se/docs/alt-svc.html\n"
384           "# This file was generated by libcurl! Edit at your own risk.\n",
385           out);
386     for(e = Curl_llist_head(&altsvc->list); e; e = n) {
387       struct altsvc *as = Curl_node_elem(e);
388       n = Curl_node_next(e);
389       result = altsvc_out(as, out);
390       if(result)
391         break;
392     }
393     fclose(out);
394     if(!result && tempstore && Curl_rename(tempstore, file))
395       result = CURLE_WRITE_ERROR;
396 
397     if(result && tempstore)
398       unlink(tempstore);
399   }
400   free(tempstore);
401   return result;
402 }
403 
getalnum(const char ** ptr,char * alpnbuf,size_t buflen)404 static CURLcode getalnum(const char **ptr, char *alpnbuf, size_t buflen)
405 {
406   size_t len;
407   const char *protop;
408   const char *p = *ptr;
409   while(*p && ISBLANK(*p))
410     p++;
411   protop = p;
412   while(*p && !ISBLANK(*p) && (*p != ';') && (*p != '='))
413     p++;
414   len = p - protop;
415   *ptr = p;
416 
417   if(!len || (len >= buflen))
418     return CURLE_BAD_FUNCTION_ARGUMENT;
419   memcpy(alpnbuf, protop, len);
420   alpnbuf[len] = 0;
421   return CURLE_OK;
422 }
423 
424 /* hostcompare() returns true if 'host' matches 'check'. The first host
425  * argument may have a trailing dot present that will be ignored.
426  */
hostcompare(const char * host,const char * check)427 static bool hostcompare(const char *host, const char *check)
428 {
429   size_t hlen = strlen(host);
430   size_t clen = strlen(check);
431 
432   if(hlen && (host[hlen - 1] == '.'))
433     hlen--;
434   if(hlen != clen)
435     /* they cannot match if they have different lengths */
436     return FALSE;
437   return strncasecompare(host, check, hlen);
438 }
439 
440 /* altsvc_flush() removes all alternatives for this source origin from the
441    list */
altsvc_flush(struct altsvcinfo * asi,enum alpnid srcalpnid,const char * srchost,unsigned short srcport)442 static void altsvc_flush(struct altsvcinfo *asi, enum alpnid srcalpnid,
443                          const char *srchost, unsigned short srcport)
444 {
445   struct Curl_llist_node *e;
446   struct Curl_llist_node *n;
447   for(e = Curl_llist_head(&asi->list); e; e = n) {
448     struct altsvc *as = Curl_node_elem(e);
449     n = Curl_node_next(e);
450     if((srcalpnid == as->src.alpnid) &&
451        (srcport == as->src.port) &&
452        hostcompare(srchost, as->src.host)) {
453       Curl_node_remove(e);
454       altsvc_free(as);
455     }
456   }
457 }
458 
459 #ifdef DEBUGBUILD
460 /* to play well with debug builds, we can *set* a fixed time this will
461    return */
altsvc_debugtime(void * unused)462 static time_t altsvc_debugtime(void *unused)
463 {
464   char *timestr = getenv("CURL_TIME");
465   (void)unused;
466   if(timestr) {
467     long val = strtol(timestr, NULL, 10);
468     return (time_t)val;
469   }
470   return time(NULL);
471 }
472 #undef time
473 #define time(x) altsvc_debugtime(x)
474 #endif
475 
476 #define ISNEWLINE(x) (((x) == '\n') || (x) == '\r')
477 
478 /*
479  * Curl_altsvc_parse() takes an incoming alt-svc response header and stores
480  * the data correctly in the cache.
481  *
482  * 'value' points to the header *value*. That is contents to the right of the
483  * header name.
484  *
485  * Currently this function rejects invalid data without returning an error.
486  * Invalid hostname, port number will result in the specific alternative
487  * being rejected. Unknown protocols are skipped.
488  */
Curl_altsvc_parse(struct Curl_easy * data,struct altsvcinfo * asi,const char * value,enum alpnid srcalpnid,const char * srchost,unsigned short srcport)489 CURLcode Curl_altsvc_parse(struct Curl_easy *data,
490                            struct altsvcinfo *asi, const char *value,
491                            enum alpnid srcalpnid, const char *srchost,
492                            unsigned short srcport)
493 {
494   const char *p = value;
495   size_t len;
496   char namebuf[MAX_ALTSVC_HOSTLEN] = "";
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 #ifdef CURL_DISABLE_VERBOSE_STRINGS
503   (void)data;
504 #endif
505   if(result) {
506     infof(data, "Excessive alt-svc header, ignoring.");
507     return CURLE_OK;
508   }
509 
510   DEBUGASSERT(asi);
511 
512   /* "clear" is a magic keyword */
513   if(strcasecompare(alpnbuf, "clear")) {
514     /* Flush cached alternatives for this source origin */
515     altsvc_flush(asi, srcalpnid, srchost, srcport);
516     return CURLE_OK;
517   }
518 
519   do {
520     if(*p == '=') {
521       /* [protocol]="[host][:port]" */
522       enum alpnid dstalpnid = alpn2alpnid(alpnbuf); /* the same by default */
523       p++;
524       if(*p == '\"') {
525         const char *dsthost = "";
526         const char *value_ptr;
527         char option[32];
528         unsigned long num;
529         char *end_ptr;
530         bool quoted = FALSE;
531         time_t maxage = 24 * 3600; /* default is 24 hours */
532         bool persist = FALSE;
533         bool valid = TRUE;
534         p++;
535         if(*p != ':') {
536           /* hostname starts here */
537           const char *hostp = p;
538           if(*p == '[') {
539             /* pass all valid IPv6 letters - does not handle zone id */
540             len = strspn(++p, "0123456789abcdefABCDEF:.");
541             if(p[len] != ']')
542               /* invalid host syntax, bail out */
543               break;
544             /* we store the IPv6 numerical address *with* brackets */
545             len += 2;
546             p = &p[len-1];
547           }
548           else {
549             while(*p && (ISALNUM(*p) || (*p == '.') || (*p == '-')))
550               p++;
551             len = p - hostp;
552           }
553           if(!len || (len >= MAX_ALTSVC_HOSTLEN)) {
554             infof(data, "Excessive alt-svc hostname, ignoring.");
555             valid = FALSE;
556           }
557           else {
558             memcpy(namebuf, hostp, len);
559             namebuf[len] = 0;
560             dsthost = namebuf;
561           }
562         }
563         else {
564           /* no destination name, use source host */
565           dsthost = srchost;
566         }
567         if(*p == ':') {
568           unsigned long port = 0;
569           p++;
570           if(ISDIGIT(*p))
571             /* a port number */
572             port = strtoul(p, &end_ptr, 10);
573           else
574             end_ptr = (char *)p; /* not left uninitialized */
575           if(!port || port > USHRT_MAX || end_ptr == p || *end_ptr != '\"') {
576             infof(data, "Unknown alt-svc port number, ignoring.");
577             valid = FALSE;
578           }
579           else {
580             dstport = curlx_ultous(port);
581             p = end_ptr;
582           }
583         }
584         if(*p++ != '\"')
585           break;
586         /* Handle the optional 'ma' and 'persist' flags. Unknown flags
587            are skipped. */
588         for(;;) {
589           while(ISBLANK(*p))
590             p++;
591           if(*p != ';')
592             break;
593           p++; /* pass the semicolon */
594           if(!*p || ISNEWLINE(*p))
595             break;
596           result = getalnum(&p, option, sizeof(option));
597           if(result) {
598             /* skip option if name is too long */
599             option[0] = '\0';
600           }
601           while(*p && ISBLANK(*p))
602             p++;
603           if(*p != '=')
604             return CURLE_OK;
605           p++;
606           while(*p && ISBLANK(*p))
607             p++;
608           if(!*p)
609             return CURLE_OK;
610           if(*p == '\"') {
611             /* quoted value */
612             p++;
613             quoted = TRUE;
614           }
615           value_ptr = p;
616           if(quoted) {
617             while(*p && *p != '\"')
618               p++;
619             if(!*p++)
620               return CURLE_OK;
621           }
622           else {
623             while(*p && !ISBLANK(*p) && *p!= ';' && *p != ',')
624               p++;
625           }
626           num = strtoul(value_ptr, &end_ptr, 10);
627           if((end_ptr != value_ptr) && (num < ULONG_MAX)) {
628             if(strcasecompare("ma", option))
629               maxage = (time_t)num;
630             else if(strcasecompare("persist", option) && (num == 1))
631               persist = TRUE;
632           }
633         }
634         if(dstalpnid && valid) {
635           if(!entries++)
636             /* Flush cached alternatives for this source origin, if any - when
637                this is the first entry of the line. */
638             altsvc_flush(asi, srcalpnid, srchost, srcport);
639 
640           as = altsvc_createid(srchost, dsthost,
641                                srcalpnid, dstalpnid,
642                                srcport, dstport);
643           if(as) {
644             /* The expires time also needs to take the Age: value (if any) into
645                account. [See RFC 7838 section 3.1] */
646             as->expires = maxage + time(NULL);
647             as->persist = persist;
648             Curl_llist_append(&asi->list, as, &as->node);
649             infof(data, "Added alt-svc: %s:%d over %s", dsthost, dstport,
650                   Curl_alpnid2str(dstalpnid));
651           }
652         }
653       }
654       else
655         break;
656       /* after the double quote there can be a comma if there is another
657          string or a semicolon if no more */
658       if(*p == ',') {
659         /* comma means another alternative is presented */
660         p++;
661         result = getalnum(&p, alpnbuf, sizeof(alpnbuf));
662         if(result)
663           break;
664       }
665     }
666     else
667       break;
668   } while(*p && (*p != ';') && (*p != '\n') && (*p != '\r'));
669 
670   return CURLE_OK;
671 }
672 
673 /*
674  * Return TRUE on a match
675  */
Curl_altsvc_lookup(struct altsvcinfo * asi,enum alpnid srcalpnid,const char * srchost,int srcport,struct altsvc ** dstentry,const int versions)676 bool Curl_altsvc_lookup(struct altsvcinfo *asi,
677                         enum alpnid srcalpnid, const char *srchost,
678                         int srcport,
679                         struct altsvc **dstentry,
680                         const int versions) /* one or more bits */
681 {
682   struct Curl_llist_node *e;
683   struct Curl_llist_node *n;
684   time_t now = time(NULL);
685   DEBUGASSERT(asi);
686   DEBUGASSERT(srchost);
687   DEBUGASSERT(dstentry);
688 
689   for(e = Curl_llist_head(&asi->list); e; e = n) {
690     struct altsvc *as = Curl_node_elem(e);
691     n = Curl_node_next(e);
692     if(as->expires < now) {
693       /* an expired entry, remove */
694       Curl_node_remove(e);
695       altsvc_free(as);
696       continue;
697     }
698     if((as->src.alpnid == srcalpnid) &&
699        hostcompare(srchost, as->src.host) &&
700        (as->src.port == srcport) &&
701        (versions & (int)as->dst.alpnid)) {
702       /* match */
703       *dstentry = as;
704       return TRUE;
705     }
706   }
707   return FALSE;
708 }
709 
710 #endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_ALTSVC */
711