quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

altsvc.c (19356B)


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