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