• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 2019, 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.haxx.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://tools.ietf.org/html/rfc7838
25  */
26 #include "curl_setup.h"
27 
28 #if !defined(CURL_DISABLE_HTTP) && defined(USE_ALTSVC)
29 #include <curl/curl.h>
30 #include "urldata.h"
31 #include "altsvc.h"
32 #include "cookie.h" /* for Curl_get_line() */
33 #include "strcase.h"
34 #include "parsedate.h"
35 #include "sendf.h"
36 #include "warnless.h"
37 
38 /* The last 3 #include files should be in this order */
39 #include "curl_printf.h"
40 #include "curl_memory.h"
41 #include "memdebug.h"
42 
43 #define MAX_ALTSVC_LINE 4095
44 #define MAX_ALTSVC_DATELENSTR "64"
45 #define MAX_ALTSVC_DATELEN 64
46 #define MAX_ALTSVC_HOSTLENSTR "512"
47 #define MAX_ALTSVC_HOSTLEN 512
48 #define MAX_ALTSVC_ALPNLENSTR "10"
49 #define MAX_ALTSVC_ALPNLEN 10
50 
alpn2alpnid(char * name)51 static enum alpnid alpn2alpnid(char *name)
52 {
53   if(strcasecompare(name, "h1"))
54     return ALPN_h1;
55   if(strcasecompare(name, "h2"))
56     return ALPN_h2;
57   if(strcasecompare(name, "h2c"))
58     return ALPN_h2c;
59   if(strcasecompare(name, "h3"))
60     return ALPN_h3;
61   return ALPN_none; /* unknown, probably rubbish input */
62 }
63 
64 /* Given the ALPN ID, return the name */
Curl_alpnid2str(enum alpnid id)65 const char *Curl_alpnid2str(enum alpnid id)
66 {
67   switch(id) {
68   case ALPN_h1:
69     return "h1";
70   case ALPN_h2:
71     return "h2";
72   case ALPN_h2c:
73     return "h2c";
74   case ALPN_h3:
75     return "h3";
76   default:
77     return ""; /* bad */
78   }
79 }
80 
81 
altsvc_free(struct altsvc * as)82 static void altsvc_free(struct altsvc *as)
83 {
84   free(as->srchost);
85   free(as->dsthost);
86   free(as);
87 }
88 
altsvc_createid(const char * srchost,const char * dsthost,enum alpnid srcalpnid,enum alpnid dstalpnid,unsigned int srcport,unsigned int dstport)89 static struct altsvc *altsvc_createid(const char *srchost,
90                                       const char *dsthost,
91                                       enum alpnid srcalpnid,
92                                       enum alpnid dstalpnid,
93                                       unsigned int srcport,
94                                       unsigned int dstport)
95 {
96   struct altsvc *as = calloc(sizeof(struct altsvc), 1);
97   if(!as)
98     return NULL;
99 
100   as->srchost = strdup(srchost);
101   if(!as->srchost)
102     goto error;
103   as->dsthost = strdup(dsthost);
104   if(!as->dsthost)
105     goto error;
106 
107   as->srcalpnid = srcalpnid;
108   as->dstalpnid = dstalpnid;
109   as->srcport = curlx_ultous(srcport);
110   as->dstport = curlx_ultous(dstport);
111 
112   return as;
113   error:
114   altsvc_free(as);
115   return NULL;
116 }
117 
altsvc_create(char * srchost,char * dsthost,char * srcalpn,char * dstalpn,unsigned int srcport,unsigned int dstport)118 static struct altsvc *altsvc_create(char *srchost,
119                                     char *dsthost,
120                                     char *srcalpn,
121                                     char *dstalpn,
122                                     unsigned int srcport,
123                                     unsigned int dstport)
124 {
125   enum alpnid dstalpnid = alpn2alpnid(dstalpn);
126   enum alpnid srcalpnid = alpn2alpnid(srcalpn);
127   if(!srcalpnid || !dstalpnid)
128     return NULL;
129   return altsvc_createid(srchost, dsthost, srcalpnid, dstalpnid,
130                          srcport, dstport);
131 }
132 
133 /* only returns SERIOUS errors */
altsvc_add(struct altsvcinfo * asi,char * line)134 static CURLcode altsvc_add(struct altsvcinfo *asi, char *line)
135 {
136   /* Example line:
137      h2 example.com 443 h3 shiny.example.com 8443 "20191231 10:00:00" 1
138    */
139   char srchost[MAX_ALTSVC_HOSTLEN + 1];
140   char dsthost[MAX_ALTSVC_HOSTLEN + 1];
141   char srcalpn[MAX_ALTSVC_ALPNLEN + 1];
142   char dstalpn[MAX_ALTSVC_ALPNLEN + 1];
143   char date[MAX_ALTSVC_DATELEN + 1];
144   unsigned int srcport;
145   unsigned int dstport;
146   unsigned int prio;
147   unsigned int persist;
148   int rc;
149 
150   rc = sscanf(line,
151               "%" MAX_ALTSVC_ALPNLENSTR "s %" MAX_ALTSVC_HOSTLENSTR "s %u "
152               "%" MAX_ALTSVC_ALPNLENSTR "s %" MAX_ALTSVC_HOSTLENSTR "s %u "
153               "\"%" MAX_ALTSVC_DATELENSTR "[^\"]\" %u %u",
154               srcalpn, srchost, &srcport,
155               dstalpn, dsthost, &dstport,
156               date, &persist, &prio);
157   if(9 == rc) {
158     struct altsvc *as;
159     time_t expires = curl_getdate(date, NULL);
160     as = altsvc_create(srchost, dsthost, srcalpn, dstalpn, srcport, dstport);
161     if(as) {
162       as->expires = expires;
163       as->prio = prio;
164       as->persist = persist ? 1 : 0;
165       Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node);
166       asi->num++; /* one more entry */
167     }
168   }
169 
170   return CURLE_OK;
171 }
172 
173 /*
174  * Load alt-svc entries from the given file. The text based line-oriented file
175  * format is documented here:
176  * https://github.com/curl/curl/wiki/QUIC-implementation
177  *
178  * This function only returns error on major problems that prevents alt-svc
179  * handling to work completely. It will ignore individual syntactical errors
180  * etc.
181  */
altsvc_load(struct altsvcinfo * asi,const char * file)182 static CURLcode altsvc_load(struct altsvcinfo *asi, const char *file)
183 {
184   CURLcode result = CURLE_OK;
185   char *line = NULL;
186   FILE *fp = fopen(file, FOPEN_READTEXT);
187   if(fp) {
188     line = malloc(MAX_ALTSVC_LINE);
189     if(!line)
190       goto fail;
191     while(Curl_get_line(line, MAX_ALTSVC_LINE, fp)) {
192       char *lineptr = line;
193       while(*lineptr && ISBLANK(*lineptr))
194         lineptr++;
195       if(*lineptr == '#')
196         /* skip commented lines */
197         continue;
198 
199       altsvc_add(asi, lineptr);
200     }
201     free(line); /* free the line buffer */
202     fclose(fp);
203   }
204   return result;
205 
206   fail:
207   free(line);
208   fclose(fp);
209   return CURLE_OUT_OF_MEMORY;
210 }
211 
212 /*
213  * Write this single altsvc entry to a single output line
214  */
215 
altsvc_out(struct altsvc * as,FILE * fp)216 static CURLcode altsvc_out(struct altsvc *as, FILE *fp)
217 {
218   struct tm stamp;
219   CURLcode result = Curl_gmtime(as->expires, &stamp);
220   if(result)
221     return result;
222 
223   fprintf(fp,
224           "%s %s %u "
225           "%s %s %u "
226           "\"%d%02d%02d "
227           "%02d:%02d:%02d\" "
228           "%u %d\n",
229           Curl_alpnid2str(as->srcalpnid), as->srchost, as->srcport,
230           Curl_alpnid2str(as->dstalpnid), as->dsthost, as->dstport,
231           stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday,
232           stamp.tm_hour, stamp.tm_min, stamp.tm_sec,
233           as->persist, as->prio);
234   return CURLE_OK;
235 }
236 
237 /* ---- library-wide functions below ---- */
238 
239 /*
240  * Curl_altsvc_init() creates a new altsvc cache.
241  * It returns the new instance or NULL if something goes wrong.
242  */
Curl_altsvc_init(void)243 struct altsvcinfo *Curl_altsvc_init(void)
244 {
245   struct altsvcinfo *asi = calloc(sizeof(struct altsvcinfo), 1);
246   if(!asi)
247     return NULL;
248   Curl_llist_init(&asi->list, NULL);
249 
250   /* set default behavior */
251   asi->flags = CURLALTSVC_H1
252 #ifdef USE_NGHTTP2
253     | CURLALTSVC_H2
254 #endif
255 #ifdef USE_HTTP3
256     /* TODO: adjust when known */
257     | CURLALTSVC_H3
258 #endif
259     ;
260   return asi;
261 }
262 
263 /*
264  * Curl_altsvc_load() loads alt-svc from file.
265  */
Curl_altsvc_load(struct altsvcinfo * asi,const char * file)266 CURLcode Curl_altsvc_load(struct altsvcinfo *asi, const char *file)
267 {
268   CURLcode result;
269   DEBUGASSERT(asi);
270   result = altsvc_load(asi, file);
271   return result;
272 }
273 
274 /*
275  * Curl_altsvc_ctrl() passes on the external bitmask.
276  */
Curl_altsvc_ctrl(struct altsvcinfo * asi,const long ctrl)277 CURLcode Curl_altsvc_ctrl(struct altsvcinfo *asi, const long ctrl)
278 {
279   DEBUGASSERT(asi);
280   if(!ctrl)
281     /* unexpected */
282     return CURLE_BAD_FUNCTION_ARGUMENT;
283   asi->flags = ctrl;
284   return CURLE_OK;
285 }
286 
287 /*
288  * Curl_altsvc_cleanup() frees an altsvc cache instance and all associated
289  * resources.
290  */
Curl_altsvc_cleanup(struct altsvcinfo * altsvc)291 void Curl_altsvc_cleanup(struct altsvcinfo *altsvc)
292 {
293   struct curl_llist_element *e;
294   struct curl_llist_element *n;
295   if(altsvc) {
296     for(e = altsvc->list.head; e; e = n) {
297       struct altsvc *as = e->ptr;
298       n = e->next;
299       altsvc_free(as);
300     }
301     free(altsvc);
302   }
303 }
304 
305 /*
306  * Curl_altsvc_save() writes the altsvc cache to a file.
307  */
Curl_altsvc_save(struct altsvcinfo * altsvc,const char * file)308 CURLcode Curl_altsvc_save(struct altsvcinfo *altsvc, const char *file)
309 {
310   struct curl_llist_element *e;
311   struct curl_llist_element *n;
312   CURLcode result = CURLE_OK;
313   FILE *out;
314 
315   if(!altsvc)
316     /* no cache activated */
317     return CURLE_OK;
318 
319   if((altsvc->flags & CURLALTSVC_READONLYFILE) || !file[0])
320     /* marked as read-only or zero length file name */
321     return CURLE_OK;
322   out = fopen(file, FOPEN_WRITETEXT);
323   if(!out)
324     return CURLE_WRITE_ERROR;
325   fputs("# Your alt-svc cache. https://curl.haxx.se/docs/alt-svc.html\n"
326         "# This file was generated by libcurl! Edit at your own risk.\n",
327         out);
328   for(e = altsvc->list.head; e; e = n) {
329     struct altsvc *as = e->ptr;
330     n = e->next;
331     result = altsvc_out(as, out);
332     if(result)
333       break;
334   }
335   fclose(out);
336   return result;
337 }
338 
getalnum(const char ** ptr,char * alpnbuf,size_t buflen)339 static CURLcode getalnum(const char **ptr, char *alpnbuf, size_t buflen)
340 {
341   size_t len;
342   const char *protop;
343   const char *p = *ptr;
344   while(*p && ISBLANK(*p))
345     p++;
346   protop = p;
347   while(*p && ISALNUM(*p))
348     p++;
349   len = p - protop;
350 
351   if(!len || (len >= buflen))
352     return CURLE_BAD_FUNCTION_ARGUMENT; /* TODO: improve error code */
353   memcpy(alpnbuf, protop, len);
354   alpnbuf[len] = 0;
355   *ptr = p;
356   return CURLE_OK;
357 }
358 
359 /* altsvc_flush() removes all alternatives for this source origin from the
360    list */
altsvc_flush(struct altsvcinfo * asi,enum alpnid srcalpnid,const char * srchost,unsigned short srcport)361 static void altsvc_flush(struct altsvcinfo *asi, enum alpnid srcalpnid,
362                          const char *srchost, unsigned short srcport)
363 {
364   struct curl_llist_element *e;
365   struct curl_llist_element *n;
366   for(e = asi->list.head; e; e = n) {
367     struct altsvc *as = e->ptr;
368     n = e->next;
369     if((srcalpnid == as->srcalpnid) &&
370        (srcport == as->srcport) &&
371        strcasecompare(srchost, as->srchost)) {
372       Curl_llist_remove(&asi->list, e, NULL);
373       altsvc_free(as);
374       asi->num--;
375     }
376   }
377 }
378 
379 #ifdef DEBUGBUILD
380 /* to play well with debug builds, we can *set* a fixed time this will
381    return */
debugtime(void * unused)382 static time_t debugtime(void *unused)
383 {
384   char *timestr = getenv("CURL_TIME");
385   (void)unused;
386   if(timestr) {
387     unsigned long val = strtol(timestr, NULL, 10);
388     return (time_t)val;
389   }
390   return time(NULL);
391 }
392 #define time(x) debugtime(x)
393 #endif
394 
395 /*
396  * Curl_altsvc_parse() takes an incoming alt-svc response header and stores
397  * the data correctly in the cache.
398  *
399  * 'value' points to the header *value*. That's contents to the right of the
400  * header name.
401  */
Curl_altsvc_parse(struct Curl_easy * data,struct altsvcinfo * asi,const char * value,enum alpnid srcalpnid,const char * srchost,unsigned short srcport)402 CURLcode Curl_altsvc_parse(struct Curl_easy *data,
403                            struct altsvcinfo *asi, const char *value,
404                            enum alpnid srcalpnid, const char *srchost,
405                            unsigned short srcport)
406 {
407   const char *p = value;
408   size_t len;
409   enum alpnid dstalpnid = srcalpnid; /* the same by default */
410   char namebuf[MAX_ALTSVC_HOSTLEN] = "";
411   char alpnbuf[MAX_ALTSVC_ALPNLEN] = "";
412   struct altsvc *as;
413   unsigned short dstport = srcport; /* the same by default */
414   const char *semip;
415   time_t maxage = 24 * 3600; /* default is 24 hours */
416   bool persist = FALSE;
417   CURLcode result = getalnum(&p, alpnbuf, sizeof(alpnbuf));
418   if(result)
419     return result;
420 
421   DEBUGASSERT(asi);
422 
423   /* Flush all cached alternatives for this source origin, if any */
424   altsvc_flush(asi, srcalpnid, srchost, srcport);
425 
426   /* "clear" is a magic keyword */
427   if(strcasecompare(alpnbuf, "clear")) {
428     /* TODO: clear whatever it is it should clear */
429     return CURLE_OK;
430   }
431 
432   /* The 'ma' and 'persist' flags are annoyingly meant for all alternatives
433      but are set after the list on the line. Scan for the semicolons and get
434      those fields first! */
435   semip = p;
436   do {
437     semip = strchr(semip, ';');
438     if(semip) {
439       char option[32];
440       unsigned long num;
441       char *end_ptr;
442       semip++; /* pass the semicolon */
443       result = getalnum(&semip, option, sizeof(option));
444       if(result)
445         break;
446       while(*semip && ISBLANK(*semip))
447         semip++;
448       if(*semip != '=')
449         continue;
450       semip++;
451       num = strtoul(semip, &end_ptr, 10);
452       if(num < ULONG_MAX) {
453         if(strcasecompare("ma", option))
454           maxage = num;
455         else if(strcasecompare("persist", option) && (num == 1))
456           persist = TRUE;
457       }
458       semip = end_ptr;
459     }
460   } while(semip);
461 
462   do {
463     if(*p == '=') {
464       /* [protocol]="[host][:port]" */
465       dstalpnid = alpn2alpnid(alpnbuf);
466       if(!dstalpnid) {
467         infof(data, "Unknown alt-svc protocol \"%s\", ignoring...\n", alpnbuf);
468         return CURLE_OK;
469       }
470       p++;
471       if(*p == '\"') {
472         const char *dsthost;
473         p++;
474         if(*p != ':') {
475           /* host name starts here */
476           const char *hostp = p;
477           while(*p && (ISALNUM(*p) || (*p == '.') || (*p == '-')))
478             p++;
479           len = p - hostp;
480           if(!len || (len >= MAX_ALTSVC_HOSTLEN))
481             return CURLE_BAD_FUNCTION_ARGUMENT; /* TODO: improve error code */
482           memcpy(namebuf, hostp, len);
483           namebuf[len] = 0;
484           dsthost = namebuf;
485         }
486         else {
487           /* no destination name, use source host */
488           dsthost = srchost;
489         }
490         if(*p == ':') {
491           /* a port number */
492           char *end_ptr;
493           unsigned long port = strtoul(++p, &end_ptr, 10);
494           if(port > USHRT_MAX || end_ptr == p || *end_ptr != '\"') {
495             infof(data, "Unknown alt-svc port number, ignoring...\n");
496             return CURLE_OK;
497           }
498           p = end_ptr;
499           dstport = curlx_ultous(port);
500         }
501         if(*p++ != '\"')
502           return CURLE_BAD_FUNCTION_ARGUMENT;
503         as = altsvc_createid(srchost, dsthost,
504                              srcalpnid, dstalpnid,
505                              srcport, dstport);
506         if(as) {
507           /* TODO: the expires time also needs to take the Age: value (if any)
508              into account. [See RFC 7838 section 3.1] */
509           as->expires = maxage + time(NULL);
510           as->persist = persist;
511           Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node);
512           asi->num++; /* one more entry */
513           infof(data, "Added alt-svc: %s:%d over %s\n", dsthost, dstport,
514                 Curl_alpnid2str(dstalpnid));
515         }
516       }
517       /* after the double quote there can be a comma if there's another
518          string or a semicolon if no more */
519       if(*p == ',') {
520         /* comma means another alternative is presented */
521         p++;
522         result = getalnum(&p, alpnbuf, sizeof(alpnbuf));
523         if(result)
524           /* failed to parse, but since we already did at least one host we
525              return OK */
526           return CURLE_OK;
527       }
528     }
529   } while(*p && (*p != ';') && (*p != '\n') && (*p != '\r'));
530 
531   return CURLE_OK;
532 }
533 
534 /*
535  * Return TRUE on a match
536  */
Curl_altsvc_lookup(struct altsvcinfo * asi,enum alpnid srcalpnid,const char * srchost,int srcport,enum alpnid * dstalpnid,const char ** dsthost,int * dstport)537 bool Curl_altsvc_lookup(struct altsvcinfo *asi,
538                         enum alpnid srcalpnid, const char *srchost,
539                         int srcport,
540                         enum alpnid *dstalpnid, const char **dsthost,
541                         int *dstport)
542 {
543   struct curl_llist_element *e;
544   struct curl_llist_element *n;
545   time_t now = time(NULL);
546   DEBUGASSERT(asi);
547   DEBUGASSERT(srchost);
548   DEBUGASSERT(dsthost);
549 
550   for(e = asi->list.head; e; e = n) {
551     struct altsvc *as = e->ptr;
552     n = e->next;
553     if(as->expires < now) {
554       /* an expired entry, remove */
555       altsvc_free(as);
556       continue;
557     }
558     if((as->srcalpnid == srcalpnid) &&
559        strcasecompare(as->srchost, srchost) &&
560        as->srcport == srcport) {
561       /* match */
562       *dstalpnid = as->dstalpnid;
563       *dsthost = as->dsthost;
564       *dstport = as->dstport;
565       return TRUE;
566     }
567   }
568   return FALSE;
569 }
570 
571 #endif /* CURL_DISABLE_HTTP || USE_ALTSVC */
572