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