/src/gdal/curl/lib/altsvc.c
Line  | Count  | Source (jump to first uncovered line)  | 
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 <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  | 0  | #define MAX_ALTSVC_LINE 4095  | 
52  | 0  | #define MAX_ALTSVC_DATELEN 256  | 
53  | 0  | #define MAX_ALTSVC_HOSTLEN 2048  | 
54  | 0  | #define MAX_ALTSVC_ALPNLEN 10  | 
55  |  |  | 
56  | 0  | #define H3VERSION "h3"  | 
57  |  |  | 
58  |  | /* Given the ALPN ID, return the name */  | 
59  |  | const char *Curl_alpnid2str(enum alpnid id)  | 
60  | 0  | { | 
61  | 0  |   switch(id) { | 
62  | 0  |   case ALPN_h1:  | 
63  | 0  |     return "h1";  | 
64  | 0  |   case ALPN_h2:  | 
65  | 0  |     return "h2";  | 
66  | 0  |   case ALPN_h3:  | 
67  | 0  |     return H3VERSION;  | 
68  | 0  |   default:  | 
69  | 0  |     return ""; /* bad */  | 
70  | 0  |   }  | 
71  | 0  | }  | 
72  |  |  | 
73  |  |  | 
74  |  | static void altsvc_free(struct altsvc *as)  | 
75  | 0  | { | 
76  | 0  |   free(as->src.host);  | 
77  | 0  |   free(as->dst.host);  | 
78  | 0  |   free(as);  | 
79  | 0  | }  | 
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  | 0  | { | 
90  | 0  |   struct altsvc *as = calloc(1, sizeof(struct altsvc));  | 
91  | 0  |   if(!as)  | 
92  | 0  |     return NULL;  | 
93  | 0  |   DEBUGASSERT(hlen);  | 
94  | 0  |   DEBUGASSERT(dlen);  | 
95  | 0  |   if(!hlen || !dlen)  | 
96  |  |     /* bad input */  | 
97  | 0  |     goto error;  | 
98  | 0  |   if((hlen > 2) && srchost[0] == '[') { | 
99  |  |     /* IPv6 address, strip off brackets */  | 
100  | 0  |     srchost++;  | 
101  | 0  |     hlen -= 2;  | 
102  | 0  |   }  | 
103  | 0  |   else if(srchost[hlen - 1] == '.') { | 
104  |  |     /* strip off trailing dot */  | 
105  | 0  |     hlen--;  | 
106  | 0  |     if(!hlen)  | 
107  | 0  |       goto error;  | 
108  | 0  |   }  | 
109  | 0  |   if((dlen > 2) && dsthost[0] == '[') { | 
110  |  |     /* IPv6 address, strip off brackets */  | 
111  | 0  |     dsthost++;  | 
112  | 0  |     dlen -= 2;  | 
113  | 0  |   }  | 
114  |  | 
  | 
115  | 0  |   as->src.host = Curl_memdup0(srchost, hlen);  | 
116  | 0  |   if(!as->src.host)  | 
117  | 0  |     goto error;  | 
118  |  |  | 
119  | 0  |   as->dst.host = Curl_memdup0(dsthost, dlen);  | 
120  | 0  |   if(!as->dst.host)  | 
121  | 0  |     goto error;  | 
122  |  |  | 
123  | 0  |   as->src.alpnid = srcalpnid;  | 
124  | 0  |   as->dst.alpnid = dstalpnid;  | 
125  | 0  |   as->src.port = (unsigned short)srcport;  | 
126  | 0  |   as->dst.port = (unsigned short)dstport;  | 
127  |  | 
  | 
128  | 0  |   return as;  | 
129  | 0  | error:  | 
130  | 0  |   altsvc_free(as);  | 
131  | 0  |   return NULL;  | 
132  | 0  | }  | 
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  | 0  | { | 
141  | 0  |   enum alpnid dstalpnid =  | 
142  | 0  |     Curl_alpn2alpnid(curlx_str(dstalpn), curlx_strlen(dstalpn));  | 
143  | 0  |   enum alpnid srcalpnid =  | 
144  | 0  |     Curl_alpn2alpnid(curlx_str(srcalpn), curlx_strlen(srcalpn));  | 
145  | 0  |   if(!srcalpnid || !dstalpnid)  | 
146  | 0  |     return NULL;  | 
147  | 0  |   return altsvc_createid(curlx_str(srchost), curlx_strlen(srchost),  | 
148  | 0  |                          curlx_str(dsthost), curlx_strlen(dsthost),  | 
149  | 0  |                          srcalpnid, dstalpnid,  | 
150  | 0  |                          srcport, dstport);  | 
151  | 0  | }  | 
152  |  |  | 
153  |  | /* only returns SERIOUS errors */  | 
154  |  | static CURLcode altsvc_add(struct altsvcinfo *asi, const char *line)  | 
155  | 0  | { | 
156  |  |   /* Example line:  | 
157  |  |      h2 example.com 443 h3 shiny.example.com 8443 "20191231 10:00:00" 1  | 
158  |  |    */  | 
159  | 0  |   struct Curl_str srchost;  | 
160  | 0  |   struct Curl_str dsthost;  | 
161  | 0  |   struct Curl_str srcalpn;  | 
162  | 0  |   struct Curl_str dstalpn;  | 
163  | 0  |   struct Curl_str date;  | 
164  | 0  |   curl_off_t srcport;  | 
165  | 0  |   curl_off_t dstport;  | 
166  | 0  |   curl_off_t persist;  | 
167  | 0  |   curl_off_t prio;  | 
168  |  | 
  | 
169  | 0  |   if(curlx_str_word(&line, &srcalpn, MAX_ALTSVC_ALPNLEN) ||  | 
170  | 0  |      curlx_str_singlespace(&line) ||  | 
171  | 0  |      curlx_str_word(&line, &srchost, MAX_ALTSVC_HOSTLEN) ||  | 
172  | 0  |      curlx_str_singlespace(&line) ||  | 
173  | 0  |      curlx_str_number(&line, &srcport, 65535) ||  | 
174  | 0  |      curlx_str_singlespace(&line) ||  | 
175  | 0  |      curlx_str_word(&line, &dstalpn, MAX_ALTSVC_ALPNLEN) ||  | 
176  | 0  |      curlx_str_singlespace(&line) ||  | 
177  | 0  |      curlx_str_word(&line, &dsthost, MAX_ALTSVC_HOSTLEN) ||  | 
178  | 0  |      curlx_str_singlespace(&line) ||  | 
179  | 0  |      curlx_str_number(&line, &dstport, 65535) ||  | 
180  | 0  |      curlx_str_singlespace(&line) ||  | 
181  | 0  |      curlx_str_quotedword(&line, &date, MAX_ALTSVC_DATELEN) ||  | 
182  | 0  |      curlx_str_singlespace(&line) ||  | 
183  | 0  |      curlx_str_number(&line, &persist, 1) ||  | 
184  | 0  |      curlx_str_singlespace(&line) ||  | 
185  | 0  |      curlx_str_number(&line, &prio, 0) ||  | 
186  | 0  |      curlx_str_newline(&line))  | 
187  | 0  |     ;  | 
188  | 0  |   else { | 
189  | 0  |     struct altsvc *as;  | 
190  | 0  |     char dbuf[MAX_ALTSVC_DATELEN + 1];  | 
191  | 0  |     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  | 0  |     memcpy(dbuf, curlx_str(&date), curlx_strlen(&date));  | 
196  | 0  |     dbuf[curlx_strlen(&date)] = 0;  | 
197  | 0  |     expires = Curl_getdate_capped(dbuf);  | 
198  | 0  |     as = altsvc_create(&srchost, &dsthost, &srcalpn, &dstalpn,  | 
199  | 0  |                        (size_t)srcport, (size_t)dstport);  | 
200  | 0  |     if(as) { | 
201  | 0  |       as->expires = expires;  | 
202  | 0  |       as->prio = 0; /* not supported to just set zero */  | 
203  | 0  |       as->persist = persist ? 1 : 0;  | 
204  | 0  |       Curl_llist_append(&asi->list, as, &as->node);  | 
205  | 0  |     }  | 
206  | 0  |   }  | 
207  |  | 
  | 
208  | 0  |   return CURLE_OK;  | 
209  | 0  | }  | 
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  | 0  | { | 
221  | 0  |   CURLcode result = CURLE_OK;  | 
222  | 0  |   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  | 0  |   free(asi->filename);  | 
227  | 0  |   asi->filename = strdup(file);  | 
228  | 0  |   if(!asi->filename)  | 
229  | 0  |     return CURLE_OUT_OF_MEMORY;  | 
230  |  |  | 
231  | 0  |   fp = fopen(file, FOPEN_READTEXT);  | 
232  | 0  |   if(fp) { | 
233  | 0  |     struct dynbuf buf;  | 
234  | 0  |     curlx_dyn_init(&buf, MAX_ALTSVC_LINE);  | 
235  | 0  |     while(Curl_get_line(&buf, fp)) { | 
236  | 0  |       const char *lineptr = curlx_dyn_ptr(&buf);  | 
237  | 0  |       curlx_str_passblanks(&lineptr);  | 
238  | 0  |       if(curlx_str_single(&lineptr, '#'))  | 
239  | 0  |         altsvc_add(asi, lineptr);  | 
240  | 0  |     }  | 
241  | 0  |     curlx_dyn_free(&buf); /* free the line buffer */  | 
242  | 0  |     fclose(fp);  | 
243  | 0  |   }  | 
244  | 0  |   return result;  | 
245  | 0  | }  | 
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  | 0  | { | 
253  | 0  |   struct tm stamp;  | 
254  | 0  |   const char *dst6_pre = "";  | 
255  | 0  |   const char *dst6_post = "";  | 
256  | 0  |   const char *src6_pre = "";  | 
257  | 0  |   const char *src6_post = "";  | 
258  | 0  |   CURLcode result = Curl_gmtime(as->expires, &stamp);  | 
259  | 0  |   if(result)  | 
260  | 0  |     return result;  | 
261  | 0  | #ifdef USE_IPV6  | 
262  | 0  |   else { | 
263  | 0  |     char ipv6_unused[16];  | 
264  | 0  |     if(1 == curlx_inet_pton(AF_INET6, as->dst.host, ipv6_unused)) { | 
265  | 0  |       dst6_pre = "[";  | 
266  | 0  |       dst6_post = "]";  | 
267  | 0  |     }  | 
268  | 0  |     if(1 == curlx_inet_pton(AF_INET6, as->src.host, ipv6_unused)) { | 
269  | 0  |       src6_pre = "[";  | 
270  | 0  |       src6_post = "]";  | 
271  | 0  |     }  | 
272  | 0  |   }  | 
273  | 0  | #endif  | 
274  | 0  |   fprintf(fp,  | 
275  | 0  |           "%s %s%s%s %u "  | 
276  | 0  |           "%s %s%s%s %u "  | 
277  | 0  |           "\"%d%02d%02d "  | 
278  | 0  |           "%02d:%02d:%02d\" "  | 
279  | 0  |           "%u %u\n",  | 
280  | 0  |           Curl_alpnid2str(as->src.alpnid),  | 
281  | 0  |           src6_pre, as->src.host, src6_post,  | 
282  | 0  |           as->src.port,  | 
283  |  | 
  | 
284  | 0  |           Curl_alpnid2str(as->dst.alpnid),  | 
285  | 0  |           dst6_pre, as->dst.host, dst6_post,  | 
286  | 0  |           as->dst.port,  | 
287  |  | 
  | 
288  | 0  |           stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday,  | 
289  | 0  |           stamp.tm_hour, stamp.tm_min, stamp.tm_sec,  | 
290  | 0  |           as->persist, as->prio);  | 
291  | 0  |   return CURLE_OK;  | 
292  | 0  | }  | 
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  | 0  | { | 
302  | 0  |   struct altsvcinfo *asi = calloc(1, sizeof(struct altsvcinfo));  | 
303  | 0  |   if(!asi)  | 
304  | 0  |     return NULL;  | 
305  | 0  |   Curl_llist_init(&asi->list, NULL);  | 
306  |  |  | 
307  |  |   /* set default behavior */  | 
308  | 0  |   asi->flags = CURLALTSVC_H1  | 
309  |  | #ifdef USE_HTTP2  | 
310  |  |     | CURLALTSVC_H2  | 
311  |  | #endif  | 
312  |  | #ifdef USE_HTTP3  | 
313  |  |     | CURLALTSVC_H3  | 
314  |  | #endif  | 
315  | 0  |     ;  | 
316  | 0  |   return asi;  | 
317  | 0  | }  | 
318  |  |  | 
319  |  | /*  | 
320  |  |  * Curl_altsvc_load() loads alt-svc from file.  | 
321  |  |  */  | 
322  |  | CURLcode Curl_altsvc_load(struct altsvcinfo *asi, const char *file)  | 
323  | 0  | { | 
324  | 0  |   DEBUGASSERT(asi);  | 
325  | 0  |   return altsvc_load(asi, file);  | 
326  | 0  | }  | 
327  |  |  | 
328  |  | /*  | 
329  |  |  * Curl_altsvc_ctrl() passes on the external bitmask.  | 
330  |  |  */  | 
331  |  | CURLcode Curl_altsvc_ctrl(struct altsvcinfo *asi, const long ctrl)  | 
332  | 0  | { | 
333  | 0  |   DEBUGASSERT(asi);  | 
334  | 0  |   asi->flags = ctrl;  | 
335  | 0  |   return CURLE_OK;  | 
336  | 0  | }  | 
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  | 142k  | { | 
344  | 142k  |   if(*altsvcp) { | 
345  | 0  |     struct Curl_llist_node *e;  | 
346  | 0  |     struct Curl_llist_node *n;  | 
347  | 0  |     struct altsvcinfo *altsvc = *altsvcp;  | 
348  | 0  |     for(e = Curl_llist_head(&altsvc->list); e; e = n) { | 
349  | 0  |       struct altsvc *as = Curl_node_elem(e);  | 
350  | 0  |       n = Curl_node_next(e);  | 
351  | 0  |       altsvc_free(as);  | 
352  | 0  |     }  | 
353  | 0  |     free(altsvc->filename);  | 
354  | 0  |     free(altsvc);  | 
355  | 0  |     *altsvcp = NULL; /* clear the pointer */  | 
356  | 0  |   }  | 
357  | 142k  | }  | 
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  | 142k  | { | 
365  | 142k  |   CURLcode result = CURLE_OK;  | 
366  | 142k  |   FILE *out;  | 
367  | 142k  |   char *tempstore = NULL;  | 
368  |  |  | 
369  | 142k  |   if(!altsvc)  | 
370  |  |     /* no cache activated */  | 
371  | 142k  |     return CURLE_OK;  | 
372  |  |  | 
373  |  |   /* if not new name is given, use the one we stored from the load */  | 
374  | 0  |   if(!file && altsvc->filename)  | 
375  | 0  |     file = altsvc->filename;  | 
376  |  | 
  | 
377  | 0  |   if((altsvc->flags & CURLALTSVC_READONLYFILE) || !file || !file[0])  | 
378  |  |     /* marked as read-only, no file or zero length filename */  | 
379  | 0  |     return CURLE_OK;  | 
380  |  |  | 
381  | 0  |   result = Curl_fopen(data, file, &out, &tempstore);  | 
382  | 0  |   if(!result) { | 
383  | 0  |     struct Curl_llist_node *e;  | 
384  | 0  |     struct Curl_llist_node *n;  | 
385  | 0  |     fputs("# Your alt-svc cache. https://curl.se/docs/alt-svc.html\n" | 
386  | 0  |           "# This file was generated by libcurl! Edit at your own risk.\n",  | 
387  | 0  |           out);  | 
388  | 0  |     for(e = Curl_llist_head(&altsvc->list); e; e = n) { | 
389  | 0  |       struct altsvc *as = Curl_node_elem(e);  | 
390  | 0  |       n = Curl_node_next(e);  | 
391  | 0  |       result = altsvc_out(as, out);  | 
392  | 0  |       if(result)  | 
393  | 0  |         break;  | 
394  | 0  |     }  | 
395  | 0  |     fclose(out);  | 
396  | 0  |     if(!result && tempstore && Curl_rename(tempstore, file))  | 
397  | 0  |       result = CURLE_WRITE_ERROR;  | 
398  |  | 
  | 
399  | 0  |     if(result && tempstore)  | 
400  | 0  |       unlink(tempstore);  | 
401  | 0  |   }  | 
402  | 0  |   free(tempstore);  | 
403  | 0  |   return result;  | 
404  | 0  | }  | 
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  | 0  | { | 
411  | 0  |   size_t hlen = strlen(host);  | 
412  | 0  |   size_t clen = strlen(check);  | 
413  |  | 
  | 
414  | 0  |   if(hlen && (host[hlen - 1] == '.'))  | 
415  | 0  |     hlen--;  | 
416  | 0  |   if(hlen != clen)  | 
417  |  |     /* they cannot match if they have different lengths */  | 
418  | 0  |     return FALSE;  | 
419  | 0  |   return strncasecompare(host, check, hlen);  | 
420  | 0  | }  | 
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  | 0  | { | 
427  | 0  |   struct Curl_llist_node *e;  | 
428  | 0  |   struct Curl_llist_node *n;  | 
429  | 0  |   for(e = Curl_llist_head(&asi->list); e; e = n) { | 
430  | 0  |     struct altsvc *as = Curl_node_elem(e);  | 
431  | 0  |     n = Curl_node_next(e);  | 
432  | 0  |     if((srcalpnid == as->src.alpnid) &&  | 
433  | 0  |        (srcport == as->src.port) &&  | 
434  | 0  |        hostcompare(srchost, as->src.host)) { | 
435  | 0  |       Curl_node_remove(e);  | 
436  | 0  |       altsvc_free(as);  | 
437  | 0  |     }  | 
438  | 0  |   }  | 
439  | 0  | }  | 
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(×tr, &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  | 0  | { | 
475  | 0  |   const char *p = value;  | 
476  | 0  |   struct altsvc *as;  | 
477  | 0  |   unsigned short dstport = srcport; /* the same by default */  | 
478  | 0  |   size_t entries = 0;  | 
479  | 0  |   struct Curl_str alpn;  | 
480  | 0  |   const char *sp;  | 
481  | 0  |   time_t maxage = 24 * 3600; /* default is 24 hours */  | 
482  | 0  |   bool persist = FALSE;  | 
483  |  | #ifdef CURL_DISABLE_VERBOSE_STRINGS  | 
484  |  |   (void)data;  | 
485  |  | #endif  | 
486  |  | 
  | 
487  | 0  |   DEBUGASSERT(asi);  | 
488  |  |  | 
489  |  |   /* initial check for "clear" */  | 
490  | 0  |   if(!curlx_str_until(&p, &alpn, MAX_ALTSVC_LINE, ';') &&  | 
491  | 0  |      !curlx_str_single(&p, ';')) { | 
492  | 0  |     curlx_str_trimblanks(&alpn);  | 
493  |  |     /* "clear" is a magic keyword */  | 
494  | 0  |     if(curlx_str_casecompare(&alpn, "clear")) { | 
495  |  |       /* Flush cached alternatives for this source origin */  | 
496  | 0  |       altsvc_flush(asi, srcalpnid, srchost, srcport);  | 
497  | 0  |       return CURLE_OK;  | 
498  | 0  |     }  | 
499  | 0  |   }  | 
500  |  |  | 
501  | 0  |   p = value;  | 
502  |  | 
  | 
503  | 0  |   if(curlx_str_until(&p, &alpn, MAX_ALTSVC_LINE, '='))  | 
504  | 0  |     return CURLE_OK; /* strange line */  | 
505  |  |  | 
506  | 0  |   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  | 0  |   sp = strchr(p, ';');  | 
511  | 0  |   if(sp) { | 
512  | 0  |     sp++; /* pass the semicolon */  | 
513  | 0  |     for(;;) { | 
514  | 0  |       struct Curl_str name;  | 
515  | 0  |       struct Curl_str val;  | 
516  | 0  |       const char *vp;  | 
517  | 0  |       curl_off_t num;  | 
518  | 0  |       bool quoted;  | 
519  |  |       /* allow some extra whitespaces around name and value */  | 
520  | 0  |       if(curlx_str_until(&sp, &name, 20, '=') ||  | 
521  | 0  |          curlx_str_single(&sp, '=') ||  | 
522  | 0  |          curlx_str_until(&sp, &val, 80, ';'))  | 
523  | 0  |         break;  | 
524  | 0  |       curlx_str_trimblanks(&name);  | 
525  | 0  |       curlx_str_trimblanks(&val);  | 
526  |  |       /* the value might be quoted */  | 
527  | 0  |       vp = curlx_str(&val);  | 
528  | 0  |       quoted = (*vp == '\"');  | 
529  | 0  |       if(quoted)  | 
530  | 0  |         vp++;  | 
531  | 0  |       if(!curlx_str_number(&vp, &num, TIME_T_MAX)) { | 
532  | 0  |         if(curlx_str_casecompare(&name, "ma"))  | 
533  | 0  |           maxage = (time_t)num;  | 
534  | 0  |         else if(curlx_str_casecompare(&name, "persist") && (num == 1))  | 
535  | 0  |           persist = TRUE;  | 
536  | 0  |       }  | 
537  | 0  |       if(quoted && curlx_str_single(&sp, '\"'))  | 
538  | 0  |         break;  | 
539  | 0  |       if(curlx_str_single(&sp, ';'))  | 
540  | 0  |         break;  | 
541  | 0  |     }  | 
542  | 0  |   }  | 
543  |  | 
  | 
544  | 0  |   do { | 
545  | 0  |     if(!curlx_str_single(&p, '=')) { | 
546  |  |       /* [protocol]="[host][:port], [protocol]="[host][:port]" */  | 
547  | 0  |       enum alpnid dstalpnid =  | 
548  | 0  |         Curl_alpn2alpnid(curlx_str(&alpn), curlx_strlen(&alpn));  | 
549  | 0  |       if(!curlx_str_single(&p, '\"')) { | 
550  | 0  |         struct Curl_str dsthost;  | 
551  | 0  |         curl_off_t port = 0;  | 
552  | 0  |         if(curlx_str_single(&p, ':')) { | 
553  |  |           /* hostname starts here */  | 
554  | 0  |           if(curlx_str_single(&p, '[')) { | 
555  | 0  |             if(curlx_str_until(&p, &dsthost, MAX_ALTSVC_HOSTLEN, ':')) { | 
556  | 0  |               infof(data, "Bad alt-svc hostname, ignoring.");  | 
557  | 0  |               break;  | 
558  | 0  |             }  | 
559  | 0  |           }  | 
560  | 0  |           else { | 
561  |  |             /* IPv6 host name */  | 
562  | 0  |             if(curlx_str_until(&p, &dsthost, MAX_IPADR_LEN, ']') ||  | 
563  | 0  |                curlx_str_single(&p, ']')) { | 
564  | 0  |               infof(data, "Bad alt-svc IPv6 hostname, ignoring.");  | 
565  | 0  |               break;  | 
566  | 0  |             }  | 
567  | 0  |           }  | 
568  | 0  |           if(curlx_str_single(&p, ':'))  | 
569  | 0  |             break;  | 
570  | 0  |         }  | 
571  | 0  |         else  | 
572  |  |           /* no destination name, use source host */  | 
573  | 0  |           curlx_str_assign(&dsthost, srchost, strlen(srchost));  | 
574  |  |  | 
575  | 0  |         if(curlx_str_number(&p, &port, 0xffff)) { | 
576  | 0  |           infof(data, "Unknown alt-svc port number, ignoring.");  | 
577  | 0  |           break;  | 
578  | 0  |         }  | 
579  |  |  | 
580  | 0  |         dstport = (unsigned short)port;  | 
581  |  | 
  | 
582  | 0  |         if(curlx_str_single(&p, '\"'))  | 
583  | 0  |           break;  | 
584  |  |  | 
585  | 0  |         if(dstalpnid) { | 
586  | 0  |           if(!entries++)  | 
587  |  |             /* Flush cached alternatives for this source origin, if any - when  | 
588  |  |                this is the first entry of the line. */  | 
589  | 0  |             altsvc_flush(asi, srcalpnid, srchost, srcport);  | 
590  |  | 
  | 
591  | 0  |           as = altsvc_createid(srchost, strlen(srchost),  | 
592  | 0  |                                curlx_str(&dsthost),  | 
593  | 0  |                                curlx_strlen(&dsthost),  | 
594  | 0  |                                srcalpnid, dstalpnid,  | 
595  | 0  |                                srcport, dstport);  | 
596  | 0  |           if(as) { | 
597  | 0  |             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  | 0  |             if(maxage > (TIME_T_MAX - secs))  | 
601  | 0  |               as->expires = TIME_T_MAX;  | 
602  | 0  |             else  | 
603  | 0  |               as->expires = maxage + secs;  | 
604  | 0  |             as->persist = persist;  | 
605  | 0  |             Curl_llist_append(&asi->list, as, &as->node);  | 
606  | 0  |             infof(data, "Added alt-svc: %.*s:%d over %s",  | 
607  | 0  |                   (int)curlx_strlen(&dsthost), curlx_str(&dsthost),  | 
608  | 0  |                   dstport, Curl_alpnid2str(dstalpnid));  | 
609  | 0  |           }  | 
610  | 0  |         }  | 
611  | 0  |       }  | 
612  | 0  |       else  | 
613  | 0  |         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  | 0  |       if(curlx_str_single(&p, ','))  | 
618  | 0  |         break;  | 
619  |  |  | 
620  |  |       /* comma means another alternative is present */  | 
621  | 0  |       if(curlx_str_until(&p, &alpn, MAX_ALTSVC_LINE, '='))  | 
622  | 0  |         break;  | 
623  | 0  |       curlx_str_trimblanks(&alpn);  | 
624  | 0  |     }  | 
625  | 0  |     else  | 
626  | 0  |       break;  | 
627  | 0  |   } while(1);  | 
628  |  |  | 
629  | 0  |   return CURLE_OK;  | 
630  | 0  | }  | 
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  | 0  | { | 
641  | 0  |   struct Curl_llist_node *e;  | 
642  | 0  |   struct Curl_llist_node *n;  | 
643  | 0  |   time_t now = time(NULL);  | 
644  | 0  |   DEBUGASSERT(asi);  | 
645  | 0  |   DEBUGASSERT(srchost);  | 
646  | 0  |   DEBUGASSERT(dstentry);  | 
647  |  | 
  | 
648  | 0  |   for(e = Curl_llist_head(&asi->list); e; e = n) { | 
649  | 0  |     struct altsvc *as = Curl_node_elem(e);  | 
650  | 0  |     n = Curl_node_next(e);  | 
651  | 0  |     if(as->expires < now) { | 
652  |  |       /* an expired entry, remove */  | 
653  | 0  |       Curl_node_remove(e);  | 
654  | 0  |       altsvc_free(as);  | 
655  | 0  |       continue;  | 
656  | 0  |     }  | 
657  | 0  |     if((as->src.alpnid == srcalpnid) &&  | 
658  | 0  |        hostcompare(srchost, as->src.host) &&  | 
659  | 0  |        (as->src.port == srcport) &&  | 
660  | 0  |        (versions & (int)as->dst.alpnid)) { | 
661  |  |       /* match */  | 
662  | 0  |       *dstentry = as;  | 
663  | 0  |       return TRUE;  | 
664  | 0  |     }  | 
665  | 0  |   }  | 
666  | 0  |   return FALSE;  | 
667  | 0  | }  | 
668  |  |  | 
669  |  | #endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_ALTSVC */  |