comparison mupdf-source/thirdparty/curl/lib/altsvc.c @ 2:b50eed0cc0ef upstream

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