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