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