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