xref: /curl/lib/altsvc.c (revision 49f83c30)
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_append(&asi->list, 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   FILE *fp;
213 
214   /* we need a private copy of the file name so that the altsvc cache file
215      name survives an easy handle reset */
216   free(asi->filename);
217   asi->filename = strdup(file);
218   if(!asi->filename)
219     return CURLE_OUT_OF_MEMORY;
220 
221   fp = fopen(file, FOPEN_READTEXT);
222   if(fp) {
223     struct dynbuf buf;
224     Curl_dyn_init(&buf, MAX_ALTSVC_LINE);
225     while(Curl_get_line(&buf, fp)) {
226       char *lineptr = Curl_dyn_ptr(&buf);
227       while(*lineptr && ISBLANK(*lineptr))
228         lineptr++;
229       if(*lineptr == '#')
230         /* skip commented lines */
231         continue;
232 
233       altsvc_add(asi, lineptr);
234     }
235     Curl_dyn_free(&buf); /* free the line buffer */
236     fclose(fp);
237   }
238   return result;
239 }
240 
241 /*
242  * Write this single altsvc entry to a single output line
243  */
244 
altsvc_out(struct altsvc * as,FILE * fp)245 static CURLcode altsvc_out(struct altsvc *as, FILE *fp)
246 {
247   struct tm stamp;
248   const char *dst6_pre = "";
249   const char *dst6_post = "";
250   const char *src6_pre = "";
251   const char *src6_post = "";
252   CURLcode result = Curl_gmtime(as->expires, &stamp);
253   if(result)
254     return result;
255 #ifdef USE_IPV6
256   else {
257     char ipv6_unused[16];
258     if(1 == Curl_inet_pton(AF_INET6, as->dst.host, ipv6_unused)) {
259       dst6_pre = "[";
260       dst6_post = "]";
261     }
262     if(1 == Curl_inet_pton(AF_INET6, as->src.host, ipv6_unused)) {
263       src6_pre = "[";
264       src6_post = "]";
265     }
266   }
267 #endif
268   fprintf(fp,
269           "%s %s%s%s %u "
270           "%s %s%s%s %u "
271           "\"%d%02d%02d "
272           "%02d:%02d:%02d\" "
273           "%u %d\n",
274           Curl_alpnid2str(as->src.alpnid),
275           src6_pre, as->src.host, src6_post,
276           as->src.port,
277 
278           Curl_alpnid2str(as->dst.alpnid),
279           dst6_pre, as->dst.host, dst6_post,
280           as->dst.port,
281 
282           stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday,
283           stamp.tm_hour, stamp.tm_min, stamp.tm_sec,
284           as->persist, as->prio);
285   return CURLE_OK;
286 }
287 
288 /* ---- library-wide functions below ---- */
289 
290 /*
291  * Curl_altsvc_init() creates a new altsvc cache.
292  * It returns the new instance or NULL if something goes wrong.
293  */
Curl_altsvc_init(void)294 struct altsvcinfo *Curl_altsvc_init(void)
295 {
296   struct altsvcinfo *asi = calloc(1, sizeof(struct altsvcinfo));
297   if(!asi)
298     return NULL;
299   Curl_llist_init(&asi->list, NULL);
300 
301   /* set default behavior */
302   asi->flags = CURLALTSVC_H1
303 #ifdef USE_HTTP2
304     | CURLALTSVC_H2
305 #endif
306 #ifdef USE_HTTP3
307     | CURLALTSVC_H3
308 #endif
309     ;
310   return asi;
311 }
312 
313 /*
314  * Curl_altsvc_load() loads alt-svc from file.
315  */
Curl_altsvc_load(struct altsvcinfo * asi,const char * file)316 CURLcode Curl_altsvc_load(struct altsvcinfo *asi, const char *file)
317 {
318   CURLcode result;
319   DEBUGASSERT(asi);
320   result = altsvc_load(asi, file);
321   return result;
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   struct Curl_llist_element *e;
341   struct Curl_llist_element *n;
342   if(*altsvcp) {
343     struct altsvcinfo *altsvc = *altsvcp;
344     for(e = altsvc->list.head; e; e = n) {
345       struct altsvc *as = e->ptr;
346       n = e->next;
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   struct Curl_llist_element *e;
362   struct Curl_llist_element *n;
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 file name */
377     return CURLE_OK;
378 
379   result = Curl_fopen(data, file, &out, &tempstore);
380   if(!result) {
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 = altsvc->list.head; e; e = n) {
385       struct altsvc *as = e->ptr;
386       n = e->next;
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 can't 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_element *e;
444   struct Curl_llist_element *n;
445   for(e = asi->list.head; e; e = n) {
446     struct altsvc *as = e->ptr;
447     n = e->next;
448     if((srcalpnid == as->src.alpnid) &&
449        (srcport == as->src.port) &&
450        hostcompare(srchost, as->src.host)) {
451       Curl_llist_remove(&asi->list, e, NULL);
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     unsigned 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's contents to the right of the
481  * header name.
482  *
483  * Currently this function rejects invalid data without returning an error.
484  * Invalid host name, 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   size_t len;
494   char namebuf[MAX_ALTSVC_HOSTLEN] = "";
495   char alpnbuf[MAX_ALTSVC_ALPNLEN] = "";
496   struct altsvc *as;
497   unsigned short dstport = srcport; /* the same by default */
498   CURLcode result = getalnum(&p, alpnbuf, sizeof(alpnbuf));
499   size_t entries = 0;
500 #ifdef CURL_DISABLE_VERBOSE_STRINGS
501   (void)data;
502 #endif
503   if(result) {
504     infof(data, "Excessive alt-svc header, ignoring.");
505     return CURLE_OK;
506   }
507 
508   DEBUGASSERT(asi);
509 
510   /* "clear" is a magic keyword */
511   if(strcasecompare(alpnbuf, "clear")) {
512     /* Flush cached alternatives for this source origin */
513     altsvc_flush(asi, srcalpnid, srchost, srcport);
514     return CURLE_OK;
515   }
516 
517   do {
518     if(*p == '=') {
519       /* [protocol]="[host][:port]" */
520       enum alpnid dstalpnid = alpn2alpnid(alpnbuf); /* the same by default */
521       p++;
522       if(*p == '\"') {
523         const char *dsthost = "";
524         const char *value_ptr;
525         char option[32];
526         unsigned long num;
527         char *end_ptr;
528         bool quoted = FALSE;
529         time_t maxage = 24 * 3600; /* default is 24 hours */
530         bool persist = FALSE;
531         bool valid = TRUE;
532         p++;
533         if(*p != ':') {
534           /* host name starts here */
535           const char *hostp = p;
536           if(*p == '[') {
537             /* pass all valid IPv6 letters - does not handle zone id */
538             len = strspn(++p, "0123456789abcdefABCDEF:.");
539             if(p[len] != ']')
540               /* invalid host syntax, bail out */
541               break;
542             /* we store the IPv6 numerical address *with* brackets */
543             len += 2;
544             p = &p[len-1];
545           }
546           else {
547             while(*p && (ISALNUM(*p) || (*p == '.') || (*p == '-')))
548               p++;
549             len = p - hostp;
550           }
551           if(!len || (len >= MAX_ALTSVC_HOSTLEN)) {
552             infof(data, "Excessive alt-svc host name, ignoring.");
553             valid = FALSE;
554           }
555           else {
556             memcpy(namebuf, hostp, len);
557             namebuf[len] = 0;
558             dsthost = namebuf;
559           }
560         }
561         else {
562           /* no destination name, use source host */
563           dsthost = srchost;
564         }
565         if(*p == ':') {
566           unsigned long port = 0;
567           p++;
568           if(ISDIGIT(*p))
569             /* a port number */
570             port = strtoul(p, &end_ptr, 10);
571           else
572             end_ptr = (char *)p; /* not left uninitialized */
573           if(!port || port > USHRT_MAX || end_ptr == p || *end_ptr != '\"') {
574             infof(data, "Unknown alt-svc port number, ignoring.");
575             valid = FALSE;
576           }
577           else {
578             dstport = curlx_ultous(port);
579             p = end_ptr;
580           }
581         }
582         if(*p++ != '\"')
583           break;
584         /* Handle the optional 'ma' and 'persist' flags. Unknown flags
585            are skipped. */
586         for(;;) {
587           while(ISBLANK(*p))
588             p++;
589           if(*p != ';')
590             break;
591           p++; /* pass the semicolon */
592           if(!*p || ISNEWLINE(*p))
593             break;
594           result = getalnum(&p, option, sizeof(option));
595           if(result) {
596             /* skip option if name is too long */
597             option[0] = '\0';
598           }
599           while(*p && ISBLANK(*p))
600             p++;
601           if(*p != '=')
602             return CURLE_OK;
603           p++;
604           while(*p && ISBLANK(*p))
605             p++;
606           if(!*p)
607             return CURLE_OK;
608           if(*p == '\"') {
609             /* quoted value */
610             p++;
611             quoted = TRUE;
612           }
613           value_ptr = p;
614           if(quoted) {
615             while(*p && *p != '\"')
616               p++;
617             if(!*p++)
618               return CURLE_OK;
619           }
620           else {
621             while(*p && !ISBLANK(*p) && *p!= ';' && *p != ',')
622               p++;
623           }
624           num = strtoul(value_ptr, &end_ptr, 10);
625           if((end_ptr != value_ptr) && (num < ULONG_MAX)) {
626             if(strcasecompare("ma", option))
627               maxage = num;
628             else if(strcasecompare("persist", option) && (num == 1))
629               persist = TRUE;
630           }
631         }
632         if(dstalpnid && valid) {
633           if(!entries++)
634             /* Flush cached alternatives for this source origin, if any - when
635                this is the first entry of the line. */
636             altsvc_flush(asi, srcalpnid, srchost, srcport);
637 
638           as = altsvc_createid(srchost, dsthost,
639                                srcalpnid, dstalpnid,
640                                srcport, dstport);
641           if(as) {
642             /* The expires time also needs to take the Age: value (if any) into
643                account. [See RFC 7838 section 3.1] */
644             as->expires = maxage + time(NULL);
645             as->persist = persist;
646             Curl_llist_append(&asi->list, as, &as->node);
647             infof(data, "Added alt-svc: %s:%d over %s", dsthost, dstport,
648                   Curl_alpnid2str(dstalpnid));
649           }
650         }
651       }
652       else
653         break;
654       /* after the double quote there can be a comma if there's another
655          string or a semicolon if no more */
656       if(*p == ',') {
657         /* comma means another alternative is presented */
658         p++;
659         result = getalnum(&p, alpnbuf, sizeof(alpnbuf));
660         if(result)
661           break;
662       }
663     }
664     else
665       break;
666   } while(*p && (*p != ';') && (*p != '\n') && (*p != '\r'));
667 
668   return CURLE_OK;
669 }
670 
671 /*
672  * Return TRUE on a match
673  */
Curl_altsvc_lookup(struct altsvcinfo * asi,enum alpnid srcalpnid,const char * srchost,int srcport,struct altsvc ** dstentry,const int versions)674 bool Curl_altsvc_lookup(struct altsvcinfo *asi,
675                         enum alpnid srcalpnid, const char *srchost,
676                         int srcport,
677                         struct altsvc **dstentry,
678                         const int versions) /* one or more bits */
679 {
680   struct Curl_llist_element *e;
681   struct Curl_llist_element *n;
682   time_t now = time(NULL);
683   DEBUGASSERT(asi);
684   DEBUGASSERT(srchost);
685   DEBUGASSERT(dstentry);
686 
687   for(e = asi->list.head; e; e = n) {
688     struct altsvc *as = e->ptr;
689     n = e->next;
690     if(as->expires < now) {
691       /* an expired entry, remove */
692       Curl_llist_remove(&asi->list, e, NULL);
693       altsvc_free(as);
694       continue;
695     }
696     if((as->src.alpnid == srcalpnid) &&
697        hostcompare(srchost, as->src.host) &&
698        (as->src.port == srcport) &&
699        (versions & as->dst.alpnid)) {
700       /* match */
701       *dstentry = as;
702       return TRUE;
703     }
704   }
705   return FALSE;
706 }
707 
708 #endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_ALTSVC */
709