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