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