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