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