Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Kitware
GitHub Repository: Kitware/CMake
Path: blob/master/Utilities/cmcurl/lib/altsvc.c
3153 views
1
/***************************************************************************
2
* _ _ ____ _
3
* Project ___| | | | _ \| |
4
* / __| | | | |_) | |
5
* | (__| |_| | _ <| |___
6
* \___|\___/|_| \_\_____|
7
*
8
* Copyright (C) Daniel Stenberg, <[email protected]>, 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 */
672
673