113498266Sopenharmony_ci/*************************************************************************** 213498266Sopenharmony_ci * _ _ ____ _ 313498266Sopenharmony_ci * Project ___| | | | _ \| | 413498266Sopenharmony_ci * / __| | | | |_) | | 513498266Sopenharmony_ci * | (__| |_| | _ <| |___ 613498266Sopenharmony_ci * \___|\___/|_| \_\_____| 713498266Sopenharmony_ci * 813498266Sopenharmony_ci * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. 913498266Sopenharmony_ci * 1013498266Sopenharmony_ci * This software is licensed as described in the file COPYING, which 1113498266Sopenharmony_ci * you should have received as part of this distribution. The terms 1213498266Sopenharmony_ci * are also available at https://curl.se/docs/copyright.html. 1313498266Sopenharmony_ci * 1413498266Sopenharmony_ci * You may opt to use, copy, modify, merge, publish, distribute and/or sell 1513498266Sopenharmony_ci * copies of the Software, and permit persons to whom the Software is 1613498266Sopenharmony_ci * furnished to do so, under the terms of the COPYING file. 1713498266Sopenharmony_ci * 1813498266Sopenharmony_ci * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 1913498266Sopenharmony_ci * KIND, either express or implied. 2013498266Sopenharmony_ci * 2113498266Sopenharmony_ci * SPDX-License-Identifier: curl 2213498266Sopenharmony_ci * 2313498266Sopenharmony_ci ***************************************************************************/ 2413498266Sopenharmony_ci/* 2513498266Sopenharmony_ci * The Alt-Svc: header is defined in RFC 7838: 2613498266Sopenharmony_ci * https://datatracker.ietf.org/doc/html/rfc7838 2713498266Sopenharmony_ci */ 2813498266Sopenharmony_ci#include "curl_setup.h" 2913498266Sopenharmony_ci 3013498266Sopenharmony_ci#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_ALTSVC) 3113498266Sopenharmony_ci#include <curl/curl.h> 3213498266Sopenharmony_ci#include "urldata.h" 3313498266Sopenharmony_ci#include "altsvc.h" 3413498266Sopenharmony_ci#include "curl_get_line.h" 3513498266Sopenharmony_ci#include "strcase.h" 3613498266Sopenharmony_ci#include "parsedate.h" 3713498266Sopenharmony_ci#include "sendf.h" 3813498266Sopenharmony_ci#include "warnless.h" 3913498266Sopenharmony_ci#include "fopen.h" 4013498266Sopenharmony_ci#include "rename.h" 4113498266Sopenharmony_ci#include "strdup.h" 4213498266Sopenharmony_ci#include "inet_pton.h" 4313498266Sopenharmony_ci 4413498266Sopenharmony_ci/* The last 3 #include files should be in this order */ 4513498266Sopenharmony_ci#include "curl_printf.h" 4613498266Sopenharmony_ci#include "curl_memory.h" 4713498266Sopenharmony_ci#include "memdebug.h" 4813498266Sopenharmony_ci 4913498266Sopenharmony_ci#define MAX_ALTSVC_LINE 4095 5013498266Sopenharmony_ci#define MAX_ALTSVC_DATELENSTR "64" 5113498266Sopenharmony_ci#define MAX_ALTSVC_DATELEN 64 5213498266Sopenharmony_ci#define MAX_ALTSVC_HOSTLENSTR "512" 5313498266Sopenharmony_ci#define MAX_ALTSVC_HOSTLEN 512 5413498266Sopenharmony_ci#define MAX_ALTSVC_ALPNLENSTR "10" 5513498266Sopenharmony_ci#define MAX_ALTSVC_ALPNLEN 10 5613498266Sopenharmony_ci 5713498266Sopenharmony_ci#define H3VERSION "h3" 5813498266Sopenharmony_ci 5913498266Sopenharmony_cistatic enum alpnid alpn2alpnid(char *name) 6013498266Sopenharmony_ci{ 6113498266Sopenharmony_ci if(strcasecompare(name, "h1")) 6213498266Sopenharmony_ci return ALPN_h1; 6313498266Sopenharmony_ci if(strcasecompare(name, "h2")) 6413498266Sopenharmony_ci return ALPN_h2; 6513498266Sopenharmony_ci if(strcasecompare(name, H3VERSION)) 6613498266Sopenharmony_ci return ALPN_h3; 6713498266Sopenharmony_ci return ALPN_none; /* unknown, probably rubbish input */ 6813498266Sopenharmony_ci} 6913498266Sopenharmony_ci 7013498266Sopenharmony_ci/* Given the ALPN ID, return the name */ 7113498266Sopenharmony_ciconst char *Curl_alpnid2str(enum alpnid id) 7213498266Sopenharmony_ci{ 7313498266Sopenharmony_ci switch(id) { 7413498266Sopenharmony_ci case ALPN_h1: 7513498266Sopenharmony_ci return "h1"; 7613498266Sopenharmony_ci case ALPN_h2: 7713498266Sopenharmony_ci return "h2"; 7813498266Sopenharmony_ci case ALPN_h3: 7913498266Sopenharmony_ci return H3VERSION; 8013498266Sopenharmony_ci default: 8113498266Sopenharmony_ci return ""; /* bad */ 8213498266Sopenharmony_ci } 8313498266Sopenharmony_ci} 8413498266Sopenharmony_ci 8513498266Sopenharmony_ci 8613498266Sopenharmony_cistatic void altsvc_free(struct altsvc *as) 8713498266Sopenharmony_ci{ 8813498266Sopenharmony_ci free(as->src.host); 8913498266Sopenharmony_ci free(as->dst.host); 9013498266Sopenharmony_ci free(as); 9113498266Sopenharmony_ci} 9213498266Sopenharmony_ci 9313498266Sopenharmony_cistatic struct altsvc *altsvc_createid(const char *srchost, 9413498266Sopenharmony_ci const char *dsthost, 9513498266Sopenharmony_ci enum alpnid srcalpnid, 9613498266Sopenharmony_ci enum alpnid dstalpnid, 9713498266Sopenharmony_ci unsigned int srcport, 9813498266Sopenharmony_ci unsigned int dstport) 9913498266Sopenharmony_ci{ 10013498266Sopenharmony_ci struct altsvc *as = calloc(1, sizeof(struct altsvc)); 10113498266Sopenharmony_ci size_t hlen; 10213498266Sopenharmony_ci size_t dlen; 10313498266Sopenharmony_ci if(!as) 10413498266Sopenharmony_ci return NULL; 10513498266Sopenharmony_ci hlen = strlen(srchost); 10613498266Sopenharmony_ci dlen = strlen(dsthost); 10713498266Sopenharmony_ci DEBUGASSERT(hlen); 10813498266Sopenharmony_ci DEBUGASSERT(dlen); 10913498266Sopenharmony_ci if(!hlen || !dlen) { 11013498266Sopenharmony_ci /* bad input */ 11113498266Sopenharmony_ci free(as); 11213498266Sopenharmony_ci return NULL; 11313498266Sopenharmony_ci } 11413498266Sopenharmony_ci if((hlen > 2) && srchost[0] == '[') { 11513498266Sopenharmony_ci /* IPv6 address, strip off brackets */ 11613498266Sopenharmony_ci srchost++; 11713498266Sopenharmony_ci hlen -= 2; 11813498266Sopenharmony_ci } 11913498266Sopenharmony_ci else if(srchost[hlen - 1] == '.') 12013498266Sopenharmony_ci /* strip off trailing dot */ 12113498266Sopenharmony_ci hlen--; 12213498266Sopenharmony_ci if((dlen > 2) && dsthost[0] == '[') { 12313498266Sopenharmony_ci /* IPv6 address, strip off brackets */ 12413498266Sopenharmony_ci dsthost++; 12513498266Sopenharmony_ci dlen -= 2; 12613498266Sopenharmony_ci } 12713498266Sopenharmony_ci 12813498266Sopenharmony_ci as->src.host = Curl_memdup0(srchost, hlen); 12913498266Sopenharmony_ci if(!as->src.host) 13013498266Sopenharmony_ci goto error; 13113498266Sopenharmony_ci 13213498266Sopenharmony_ci as->dst.host = Curl_memdup0(dsthost, dlen); 13313498266Sopenharmony_ci if(!as->dst.host) 13413498266Sopenharmony_ci goto error; 13513498266Sopenharmony_ci 13613498266Sopenharmony_ci as->src.alpnid = srcalpnid; 13713498266Sopenharmony_ci as->dst.alpnid = dstalpnid; 13813498266Sopenharmony_ci as->src.port = curlx_ultous(srcport); 13913498266Sopenharmony_ci as->dst.port = curlx_ultous(dstport); 14013498266Sopenharmony_ci 14113498266Sopenharmony_ci return as; 14213498266Sopenharmony_cierror: 14313498266Sopenharmony_ci altsvc_free(as); 14413498266Sopenharmony_ci return NULL; 14513498266Sopenharmony_ci} 14613498266Sopenharmony_ci 14713498266Sopenharmony_cistatic struct altsvc *altsvc_create(char *srchost, 14813498266Sopenharmony_ci char *dsthost, 14913498266Sopenharmony_ci char *srcalpn, 15013498266Sopenharmony_ci char *dstalpn, 15113498266Sopenharmony_ci unsigned int srcport, 15213498266Sopenharmony_ci unsigned int dstport) 15313498266Sopenharmony_ci{ 15413498266Sopenharmony_ci enum alpnid dstalpnid = alpn2alpnid(dstalpn); 15513498266Sopenharmony_ci enum alpnid srcalpnid = alpn2alpnid(srcalpn); 15613498266Sopenharmony_ci if(!srcalpnid || !dstalpnid) 15713498266Sopenharmony_ci return NULL; 15813498266Sopenharmony_ci return altsvc_createid(srchost, dsthost, srcalpnid, dstalpnid, 15913498266Sopenharmony_ci srcport, dstport); 16013498266Sopenharmony_ci} 16113498266Sopenharmony_ci 16213498266Sopenharmony_ci/* only returns SERIOUS errors */ 16313498266Sopenharmony_cistatic CURLcode altsvc_add(struct altsvcinfo *asi, char *line) 16413498266Sopenharmony_ci{ 16513498266Sopenharmony_ci /* Example line: 16613498266Sopenharmony_ci h2 example.com 443 h3 shiny.example.com 8443 "20191231 10:00:00" 1 16713498266Sopenharmony_ci */ 16813498266Sopenharmony_ci char srchost[MAX_ALTSVC_HOSTLEN + 1]; 16913498266Sopenharmony_ci char dsthost[MAX_ALTSVC_HOSTLEN + 1]; 17013498266Sopenharmony_ci char srcalpn[MAX_ALTSVC_ALPNLEN + 1]; 17113498266Sopenharmony_ci char dstalpn[MAX_ALTSVC_ALPNLEN + 1]; 17213498266Sopenharmony_ci char date[MAX_ALTSVC_DATELEN + 1]; 17313498266Sopenharmony_ci unsigned int srcport; 17413498266Sopenharmony_ci unsigned int dstport; 17513498266Sopenharmony_ci unsigned int prio; 17613498266Sopenharmony_ci unsigned int persist; 17713498266Sopenharmony_ci int rc; 17813498266Sopenharmony_ci 17913498266Sopenharmony_ci rc = sscanf(line, 18013498266Sopenharmony_ci "%" MAX_ALTSVC_ALPNLENSTR "s %" MAX_ALTSVC_HOSTLENSTR "s %u " 18113498266Sopenharmony_ci "%" MAX_ALTSVC_ALPNLENSTR "s %" MAX_ALTSVC_HOSTLENSTR "s %u " 18213498266Sopenharmony_ci "\"%" MAX_ALTSVC_DATELENSTR "[^\"]\" %u %u", 18313498266Sopenharmony_ci srcalpn, srchost, &srcport, 18413498266Sopenharmony_ci dstalpn, dsthost, &dstport, 18513498266Sopenharmony_ci date, &persist, &prio); 18613498266Sopenharmony_ci if(9 == rc) { 18713498266Sopenharmony_ci struct altsvc *as; 18813498266Sopenharmony_ci time_t expires = Curl_getdate_capped(date); 18913498266Sopenharmony_ci as = altsvc_create(srchost, dsthost, srcalpn, dstalpn, srcport, dstport); 19013498266Sopenharmony_ci if(as) { 19113498266Sopenharmony_ci as->expires = expires; 19213498266Sopenharmony_ci as->prio = prio; 19313498266Sopenharmony_ci as->persist = persist ? 1 : 0; 19413498266Sopenharmony_ci Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node); 19513498266Sopenharmony_ci } 19613498266Sopenharmony_ci } 19713498266Sopenharmony_ci 19813498266Sopenharmony_ci return CURLE_OK; 19913498266Sopenharmony_ci} 20013498266Sopenharmony_ci 20113498266Sopenharmony_ci/* 20213498266Sopenharmony_ci * Load alt-svc entries from the given file. The text based line-oriented file 20313498266Sopenharmony_ci * format is documented here: https://curl.se/docs/alt-svc.html 20413498266Sopenharmony_ci * 20513498266Sopenharmony_ci * This function only returns error on major problems that prevent alt-svc 20613498266Sopenharmony_ci * handling to work completely. It will ignore individual syntactical errors 20713498266Sopenharmony_ci * etc. 20813498266Sopenharmony_ci */ 20913498266Sopenharmony_cistatic CURLcode altsvc_load(struct altsvcinfo *asi, const char *file) 21013498266Sopenharmony_ci{ 21113498266Sopenharmony_ci CURLcode result = CURLE_OK; 21213498266Sopenharmony_ci char *line = NULL; 21313498266Sopenharmony_ci FILE *fp; 21413498266Sopenharmony_ci 21513498266Sopenharmony_ci /* we need a private copy of the file name so that the altsvc cache file 21613498266Sopenharmony_ci name survives an easy handle reset */ 21713498266Sopenharmony_ci free(asi->filename); 21813498266Sopenharmony_ci asi->filename = strdup(file); 21913498266Sopenharmony_ci if(!asi->filename) 22013498266Sopenharmony_ci return CURLE_OUT_OF_MEMORY; 22113498266Sopenharmony_ci 22213498266Sopenharmony_ci fp = fopen(file, FOPEN_READTEXT); 22313498266Sopenharmony_ci if(fp) { 22413498266Sopenharmony_ci line = malloc(MAX_ALTSVC_LINE); 22513498266Sopenharmony_ci if(!line) 22613498266Sopenharmony_ci goto fail; 22713498266Sopenharmony_ci while(Curl_get_line(line, MAX_ALTSVC_LINE, fp)) { 22813498266Sopenharmony_ci char *lineptr = line; 22913498266Sopenharmony_ci while(*lineptr && ISBLANK(*lineptr)) 23013498266Sopenharmony_ci lineptr++; 23113498266Sopenharmony_ci if(*lineptr == '#') 23213498266Sopenharmony_ci /* skip commented lines */ 23313498266Sopenharmony_ci continue; 23413498266Sopenharmony_ci 23513498266Sopenharmony_ci altsvc_add(asi, lineptr); 23613498266Sopenharmony_ci } 23713498266Sopenharmony_ci free(line); /* free the line buffer */ 23813498266Sopenharmony_ci fclose(fp); 23913498266Sopenharmony_ci } 24013498266Sopenharmony_ci return result; 24113498266Sopenharmony_ci 24213498266Sopenharmony_cifail: 24313498266Sopenharmony_ci Curl_safefree(asi->filename); 24413498266Sopenharmony_ci free(line); 24513498266Sopenharmony_ci fclose(fp); 24613498266Sopenharmony_ci return CURLE_OUT_OF_MEMORY; 24713498266Sopenharmony_ci} 24813498266Sopenharmony_ci 24913498266Sopenharmony_ci/* 25013498266Sopenharmony_ci * Write this single altsvc entry to a single output line 25113498266Sopenharmony_ci */ 25213498266Sopenharmony_ci 25313498266Sopenharmony_cistatic CURLcode altsvc_out(struct altsvc *as, FILE *fp) 25413498266Sopenharmony_ci{ 25513498266Sopenharmony_ci struct tm stamp; 25613498266Sopenharmony_ci const char *dst6_pre = ""; 25713498266Sopenharmony_ci const char *dst6_post = ""; 25813498266Sopenharmony_ci const char *src6_pre = ""; 25913498266Sopenharmony_ci const char *src6_post = ""; 26013498266Sopenharmony_ci CURLcode result = Curl_gmtime(as->expires, &stamp); 26113498266Sopenharmony_ci if(result) 26213498266Sopenharmony_ci return result; 26313498266Sopenharmony_ci#ifdef ENABLE_IPV6 26413498266Sopenharmony_ci else { 26513498266Sopenharmony_ci char ipv6_unused[16]; 26613498266Sopenharmony_ci if(1 == Curl_inet_pton(AF_INET6, as->dst.host, ipv6_unused)) { 26713498266Sopenharmony_ci dst6_pre = "["; 26813498266Sopenharmony_ci dst6_post = "]"; 26913498266Sopenharmony_ci } 27013498266Sopenharmony_ci if(1 == Curl_inet_pton(AF_INET6, as->src.host, ipv6_unused)) { 27113498266Sopenharmony_ci src6_pre = "["; 27213498266Sopenharmony_ci src6_post = "]"; 27313498266Sopenharmony_ci } 27413498266Sopenharmony_ci } 27513498266Sopenharmony_ci#endif 27613498266Sopenharmony_ci fprintf(fp, 27713498266Sopenharmony_ci "%s %s%s%s %u " 27813498266Sopenharmony_ci "%s %s%s%s %u " 27913498266Sopenharmony_ci "\"%d%02d%02d " 28013498266Sopenharmony_ci "%02d:%02d:%02d\" " 28113498266Sopenharmony_ci "%u %d\n", 28213498266Sopenharmony_ci Curl_alpnid2str(as->src.alpnid), 28313498266Sopenharmony_ci src6_pre, as->src.host, src6_post, 28413498266Sopenharmony_ci as->src.port, 28513498266Sopenharmony_ci 28613498266Sopenharmony_ci Curl_alpnid2str(as->dst.alpnid), 28713498266Sopenharmony_ci dst6_pre, as->dst.host, dst6_post, 28813498266Sopenharmony_ci as->dst.port, 28913498266Sopenharmony_ci 29013498266Sopenharmony_ci stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday, 29113498266Sopenharmony_ci stamp.tm_hour, stamp.tm_min, stamp.tm_sec, 29213498266Sopenharmony_ci as->persist, as->prio); 29313498266Sopenharmony_ci return CURLE_OK; 29413498266Sopenharmony_ci} 29513498266Sopenharmony_ci 29613498266Sopenharmony_ci/* ---- library-wide functions below ---- */ 29713498266Sopenharmony_ci 29813498266Sopenharmony_ci/* 29913498266Sopenharmony_ci * Curl_altsvc_init() creates a new altsvc cache. 30013498266Sopenharmony_ci * It returns the new instance or NULL if something goes wrong. 30113498266Sopenharmony_ci */ 30213498266Sopenharmony_cistruct altsvcinfo *Curl_altsvc_init(void) 30313498266Sopenharmony_ci{ 30413498266Sopenharmony_ci struct altsvcinfo *asi = calloc(1, sizeof(struct altsvcinfo)); 30513498266Sopenharmony_ci if(!asi) 30613498266Sopenharmony_ci return NULL; 30713498266Sopenharmony_ci Curl_llist_init(&asi->list, NULL); 30813498266Sopenharmony_ci 30913498266Sopenharmony_ci /* set default behavior */ 31013498266Sopenharmony_ci asi->flags = CURLALTSVC_H1 31113498266Sopenharmony_ci#ifdef USE_HTTP2 31213498266Sopenharmony_ci | CURLALTSVC_H2 31313498266Sopenharmony_ci#endif 31413498266Sopenharmony_ci#ifdef ENABLE_QUIC 31513498266Sopenharmony_ci | CURLALTSVC_H3 31613498266Sopenharmony_ci#endif 31713498266Sopenharmony_ci ; 31813498266Sopenharmony_ci return asi; 31913498266Sopenharmony_ci} 32013498266Sopenharmony_ci 32113498266Sopenharmony_ci/* 32213498266Sopenharmony_ci * Curl_altsvc_load() loads alt-svc from file. 32313498266Sopenharmony_ci */ 32413498266Sopenharmony_ciCURLcode Curl_altsvc_load(struct altsvcinfo *asi, const char *file) 32513498266Sopenharmony_ci{ 32613498266Sopenharmony_ci CURLcode result; 32713498266Sopenharmony_ci DEBUGASSERT(asi); 32813498266Sopenharmony_ci result = altsvc_load(asi, file); 32913498266Sopenharmony_ci return result; 33013498266Sopenharmony_ci} 33113498266Sopenharmony_ci 33213498266Sopenharmony_ci/* 33313498266Sopenharmony_ci * Curl_altsvc_ctrl() passes on the external bitmask. 33413498266Sopenharmony_ci */ 33513498266Sopenharmony_ciCURLcode Curl_altsvc_ctrl(struct altsvcinfo *asi, const long ctrl) 33613498266Sopenharmony_ci{ 33713498266Sopenharmony_ci DEBUGASSERT(asi); 33813498266Sopenharmony_ci asi->flags = ctrl; 33913498266Sopenharmony_ci return CURLE_OK; 34013498266Sopenharmony_ci} 34113498266Sopenharmony_ci 34213498266Sopenharmony_ci/* 34313498266Sopenharmony_ci * Curl_altsvc_cleanup() frees an altsvc cache instance and all associated 34413498266Sopenharmony_ci * resources. 34513498266Sopenharmony_ci */ 34613498266Sopenharmony_civoid Curl_altsvc_cleanup(struct altsvcinfo **altsvcp) 34713498266Sopenharmony_ci{ 34813498266Sopenharmony_ci struct Curl_llist_element *e; 34913498266Sopenharmony_ci struct Curl_llist_element *n; 35013498266Sopenharmony_ci if(*altsvcp) { 35113498266Sopenharmony_ci struct altsvcinfo *altsvc = *altsvcp; 35213498266Sopenharmony_ci for(e = altsvc->list.head; e; e = n) { 35313498266Sopenharmony_ci struct altsvc *as = e->ptr; 35413498266Sopenharmony_ci n = e->next; 35513498266Sopenharmony_ci altsvc_free(as); 35613498266Sopenharmony_ci } 35713498266Sopenharmony_ci free(altsvc->filename); 35813498266Sopenharmony_ci free(altsvc); 35913498266Sopenharmony_ci *altsvcp = NULL; /* clear the pointer */ 36013498266Sopenharmony_ci } 36113498266Sopenharmony_ci} 36213498266Sopenharmony_ci 36313498266Sopenharmony_ci/* 36413498266Sopenharmony_ci * Curl_altsvc_save() writes the altsvc cache to a file. 36513498266Sopenharmony_ci */ 36613498266Sopenharmony_ciCURLcode Curl_altsvc_save(struct Curl_easy *data, 36713498266Sopenharmony_ci struct altsvcinfo *altsvc, const char *file) 36813498266Sopenharmony_ci{ 36913498266Sopenharmony_ci struct Curl_llist_element *e; 37013498266Sopenharmony_ci struct Curl_llist_element *n; 37113498266Sopenharmony_ci CURLcode result = CURLE_OK; 37213498266Sopenharmony_ci FILE *out; 37313498266Sopenharmony_ci char *tempstore = NULL; 37413498266Sopenharmony_ci 37513498266Sopenharmony_ci if(!altsvc) 37613498266Sopenharmony_ci /* no cache activated */ 37713498266Sopenharmony_ci return CURLE_OK; 37813498266Sopenharmony_ci 37913498266Sopenharmony_ci /* if not new name is given, use the one we stored from the load */ 38013498266Sopenharmony_ci if(!file && altsvc->filename) 38113498266Sopenharmony_ci file = altsvc->filename; 38213498266Sopenharmony_ci 38313498266Sopenharmony_ci if((altsvc->flags & CURLALTSVC_READONLYFILE) || !file || !file[0]) 38413498266Sopenharmony_ci /* marked as read-only, no file or zero length file name */ 38513498266Sopenharmony_ci return CURLE_OK; 38613498266Sopenharmony_ci 38713498266Sopenharmony_ci result = Curl_fopen(data, file, &out, &tempstore); 38813498266Sopenharmony_ci if(!result) { 38913498266Sopenharmony_ci fputs("# Your alt-svc cache. https://curl.se/docs/alt-svc.html\n" 39013498266Sopenharmony_ci "# This file was generated by libcurl! Edit at your own risk.\n", 39113498266Sopenharmony_ci out); 39213498266Sopenharmony_ci for(e = altsvc->list.head; e; e = n) { 39313498266Sopenharmony_ci struct altsvc *as = e->ptr; 39413498266Sopenharmony_ci n = e->next; 39513498266Sopenharmony_ci result = altsvc_out(as, out); 39613498266Sopenharmony_ci if(result) 39713498266Sopenharmony_ci break; 39813498266Sopenharmony_ci } 39913498266Sopenharmony_ci fclose(out); 40013498266Sopenharmony_ci if(!result && tempstore && Curl_rename(tempstore, file)) 40113498266Sopenharmony_ci result = CURLE_WRITE_ERROR; 40213498266Sopenharmony_ci 40313498266Sopenharmony_ci if(result && tempstore) 40413498266Sopenharmony_ci unlink(tempstore); 40513498266Sopenharmony_ci } 40613498266Sopenharmony_ci free(tempstore); 40713498266Sopenharmony_ci return result; 40813498266Sopenharmony_ci} 40913498266Sopenharmony_ci 41013498266Sopenharmony_cistatic CURLcode getalnum(const char **ptr, char *alpnbuf, size_t buflen) 41113498266Sopenharmony_ci{ 41213498266Sopenharmony_ci size_t len; 41313498266Sopenharmony_ci const char *protop; 41413498266Sopenharmony_ci const char *p = *ptr; 41513498266Sopenharmony_ci while(*p && ISBLANK(*p)) 41613498266Sopenharmony_ci p++; 41713498266Sopenharmony_ci protop = p; 41813498266Sopenharmony_ci while(*p && !ISBLANK(*p) && (*p != ';') && (*p != '=')) 41913498266Sopenharmony_ci p++; 42013498266Sopenharmony_ci len = p - protop; 42113498266Sopenharmony_ci *ptr = p; 42213498266Sopenharmony_ci 42313498266Sopenharmony_ci if(!len || (len >= buflen)) 42413498266Sopenharmony_ci return CURLE_BAD_FUNCTION_ARGUMENT; 42513498266Sopenharmony_ci memcpy(alpnbuf, protop, len); 42613498266Sopenharmony_ci alpnbuf[len] = 0; 42713498266Sopenharmony_ci return CURLE_OK; 42813498266Sopenharmony_ci} 42913498266Sopenharmony_ci 43013498266Sopenharmony_ci/* hostcompare() returns true if 'host' matches 'check'. The first host 43113498266Sopenharmony_ci * argument may have a trailing dot present that will be ignored. 43213498266Sopenharmony_ci */ 43313498266Sopenharmony_cistatic bool hostcompare(const char *host, const char *check) 43413498266Sopenharmony_ci{ 43513498266Sopenharmony_ci size_t hlen = strlen(host); 43613498266Sopenharmony_ci size_t clen = strlen(check); 43713498266Sopenharmony_ci 43813498266Sopenharmony_ci if(hlen && (host[hlen - 1] == '.')) 43913498266Sopenharmony_ci hlen--; 44013498266Sopenharmony_ci if(hlen != clen) 44113498266Sopenharmony_ci /* they can't match if they have different lengths */ 44213498266Sopenharmony_ci return FALSE; 44313498266Sopenharmony_ci return strncasecompare(host, check, hlen); 44413498266Sopenharmony_ci} 44513498266Sopenharmony_ci 44613498266Sopenharmony_ci/* altsvc_flush() removes all alternatives for this source origin from the 44713498266Sopenharmony_ci list */ 44813498266Sopenharmony_cistatic void altsvc_flush(struct altsvcinfo *asi, enum alpnid srcalpnid, 44913498266Sopenharmony_ci const char *srchost, unsigned short srcport) 45013498266Sopenharmony_ci{ 45113498266Sopenharmony_ci struct Curl_llist_element *e; 45213498266Sopenharmony_ci struct Curl_llist_element *n; 45313498266Sopenharmony_ci for(e = asi->list.head; e; e = n) { 45413498266Sopenharmony_ci struct altsvc *as = e->ptr; 45513498266Sopenharmony_ci n = e->next; 45613498266Sopenharmony_ci if((srcalpnid == as->src.alpnid) && 45713498266Sopenharmony_ci (srcport == as->src.port) && 45813498266Sopenharmony_ci hostcompare(srchost, as->src.host)) { 45913498266Sopenharmony_ci Curl_llist_remove(&asi->list, e, NULL); 46013498266Sopenharmony_ci altsvc_free(as); 46113498266Sopenharmony_ci } 46213498266Sopenharmony_ci } 46313498266Sopenharmony_ci} 46413498266Sopenharmony_ci 46513498266Sopenharmony_ci#ifdef DEBUGBUILD 46613498266Sopenharmony_ci/* to play well with debug builds, we can *set* a fixed time this will 46713498266Sopenharmony_ci return */ 46813498266Sopenharmony_cistatic time_t altsvc_debugtime(void *unused) 46913498266Sopenharmony_ci{ 47013498266Sopenharmony_ci char *timestr = getenv("CURL_TIME"); 47113498266Sopenharmony_ci (void)unused; 47213498266Sopenharmony_ci if(timestr) { 47313498266Sopenharmony_ci unsigned long val = strtol(timestr, NULL, 10); 47413498266Sopenharmony_ci return (time_t)val; 47513498266Sopenharmony_ci } 47613498266Sopenharmony_ci return time(NULL); 47713498266Sopenharmony_ci} 47813498266Sopenharmony_ci#undef time 47913498266Sopenharmony_ci#define time(x) altsvc_debugtime(x) 48013498266Sopenharmony_ci#endif 48113498266Sopenharmony_ci 48213498266Sopenharmony_ci#define ISNEWLINE(x) (((x) == '\n') || (x) == '\r') 48313498266Sopenharmony_ci 48413498266Sopenharmony_ci/* 48513498266Sopenharmony_ci * Curl_altsvc_parse() takes an incoming alt-svc response header and stores 48613498266Sopenharmony_ci * the data correctly in the cache. 48713498266Sopenharmony_ci * 48813498266Sopenharmony_ci * 'value' points to the header *value*. That's contents to the right of the 48913498266Sopenharmony_ci * header name. 49013498266Sopenharmony_ci * 49113498266Sopenharmony_ci * Currently this function rejects invalid data without returning an error. 49213498266Sopenharmony_ci * Invalid host name, port number will result in the specific alternative 49313498266Sopenharmony_ci * being rejected. Unknown protocols are skipped. 49413498266Sopenharmony_ci */ 49513498266Sopenharmony_ciCURLcode Curl_altsvc_parse(struct Curl_easy *data, 49613498266Sopenharmony_ci struct altsvcinfo *asi, const char *value, 49713498266Sopenharmony_ci enum alpnid srcalpnid, const char *srchost, 49813498266Sopenharmony_ci unsigned short srcport) 49913498266Sopenharmony_ci{ 50013498266Sopenharmony_ci const char *p = value; 50113498266Sopenharmony_ci size_t len; 50213498266Sopenharmony_ci char namebuf[MAX_ALTSVC_HOSTLEN] = ""; 50313498266Sopenharmony_ci char alpnbuf[MAX_ALTSVC_ALPNLEN] = ""; 50413498266Sopenharmony_ci struct altsvc *as; 50513498266Sopenharmony_ci unsigned short dstport = srcport; /* the same by default */ 50613498266Sopenharmony_ci CURLcode result = getalnum(&p, alpnbuf, sizeof(alpnbuf)); 50713498266Sopenharmony_ci size_t entries = 0; 50813498266Sopenharmony_ci#ifdef CURL_DISABLE_VERBOSE_STRINGS 50913498266Sopenharmony_ci (void)data; 51013498266Sopenharmony_ci#endif 51113498266Sopenharmony_ci if(result) { 51213498266Sopenharmony_ci infof(data, "Excessive alt-svc header, ignoring."); 51313498266Sopenharmony_ci return CURLE_OK; 51413498266Sopenharmony_ci } 51513498266Sopenharmony_ci 51613498266Sopenharmony_ci DEBUGASSERT(asi); 51713498266Sopenharmony_ci 51813498266Sopenharmony_ci /* "clear" is a magic keyword */ 51913498266Sopenharmony_ci if(strcasecompare(alpnbuf, "clear")) { 52013498266Sopenharmony_ci /* Flush cached alternatives for this source origin */ 52113498266Sopenharmony_ci altsvc_flush(asi, srcalpnid, srchost, srcport); 52213498266Sopenharmony_ci return CURLE_OK; 52313498266Sopenharmony_ci } 52413498266Sopenharmony_ci 52513498266Sopenharmony_ci do { 52613498266Sopenharmony_ci if(*p == '=') { 52713498266Sopenharmony_ci /* [protocol]="[host][:port]" */ 52813498266Sopenharmony_ci enum alpnid dstalpnid = alpn2alpnid(alpnbuf); /* the same by default */ 52913498266Sopenharmony_ci p++; 53013498266Sopenharmony_ci if(*p == '\"') { 53113498266Sopenharmony_ci const char *dsthost = ""; 53213498266Sopenharmony_ci const char *value_ptr; 53313498266Sopenharmony_ci char option[32]; 53413498266Sopenharmony_ci unsigned long num; 53513498266Sopenharmony_ci char *end_ptr; 53613498266Sopenharmony_ci bool quoted = FALSE; 53713498266Sopenharmony_ci time_t maxage = 24 * 3600; /* default is 24 hours */ 53813498266Sopenharmony_ci bool persist = FALSE; 53913498266Sopenharmony_ci bool valid = TRUE; 54013498266Sopenharmony_ci p++; 54113498266Sopenharmony_ci if(*p != ':') { 54213498266Sopenharmony_ci /* host name starts here */ 54313498266Sopenharmony_ci const char *hostp = p; 54413498266Sopenharmony_ci if(*p == '[') { 54513498266Sopenharmony_ci /* pass all valid IPv6 letters - does not handle zone id */ 54613498266Sopenharmony_ci len = strspn(++p, "0123456789abcdefABCDEF:."); 54713498266Sopenharmony_ci if(p[len] != ']') 54813498266Sopenharmony_ci /* invalid host syntax, bail out */ 54913498266Sopenharmony_ci break; 55013498266Sopenharmony_ci /* we store the IPv6 numerical address *with* brackets */ 55113498266Sopenharmony_ci len += 2; 55213498266Sopenharmony_ci p = &p[len-1]; 55313498266Sopenharmony_ci } 55413498266Sopenharmony_ci else { 55513498266Sopenharmony_ci while(*p && (ISALNUM(*p) || (*p == '.') || (*p == '-'))) 55613498266Sopenharmony_ci p++; 55713498266Sopenharmony_ci len = p - hostp; 55813498266Sopenharmony_ci } 55913498266Sopenharmony_ci if(!len || (len >= MAX_ALTSVC_HOSTLEN)) { 56013498266Sopenharmony_ci infof(data, "Excessive alt-svc host name, ignoring."); 56113498266Sopenharmony_ci valid = FALSE; 56213498266Sopenharmony_ci } 56313498266Sopenharmony_ci else { 56413498266Sopenharmony_ci memcpy(namebuf, hostp, len); 56513498266Sopenharmony_ci namebuf[len] = 0; 56613498266Sopenharmony_ci dsthost = namebuf; 56713498266Sopenharmony_ci } 56813498266Sopenharmony_ci } 56913498266Sopenharmony_ci else { 57013498266Sopenharmony_ci /* no destination name, use source host */ 57113498266Sopenharmony_ci dsthost = srchost; 57213498266Sopenharmony_ci } 57313498266Sopenharmony_ci if(*p == ':') { 57413498266Sopenharmony_ci unsigned long port = 0; 57513498266Sopenharmony_ci p++; 57613498266Sopenharmony_ci if(ISDIGIT(*p)) 57713498266Sopenharmony_ci /* a port number */ 57813498266Sopenharmony_ci port = strtoul(p, &end_ptr, 10); 57913498266Sopenharmony_ci else 58013498266Sopenharmony_ci end_ptr = (char *)p; /* not left uninitialized */ 58113498266Sopenharmony_ci if(!port || port > USHRT_MAX || end_ptr == p || *end_ptr != '\"') { 58213498266Sopenharmony_ci infof(data, "Unknown alt-svc port number, ignoring."); 58313498266Sopenharmony_ci valid = FALSE; 58413498266Sopenharmony_ci } 58513498266Sopenharmony_ci else { 58613498266Sopenharmony_ci dstport = curlx_ultous(port); 58713498266Sopenharmony_ci p = end_ptr; 58813498266Sopenharmony_ci } 58913498266Sopenharmony_ci } 59013498266Sopenharmony_ci if(*p++ != '\"') 59113498266Sopenharmony_ci break; 59213498266Sopenharmony_ci /* Handle the optional 'ma' and 'persist' flags. Unknown flags 59313498266Sopenharmony_ci are skipped. */ 59413498266Sopenharmony_ci for(;;) { 59513498266Sopenharmony_ci while(ISBLANK(*p)) 59613498266Sopenharmony_ci p++; 59713498266Sopenharmony_ci if(*p != ';') 59813498266Sopenharmony_ci break; 59913498266Sopenharmony_ci p++; /* pass the semicolon */ 60013498266Sopenharmony_ci if(!*p || ISNEWLINE(*p)) 60113498266Sopenharmony_ci break; 60213498266Sopenharmony_ci result = getalnum(&p, option, sizeof(option)); 60313498266Sopenharmony_ci if(result) { 60413498266Sopenharmony_ci /* skip option if name is too long */ 60513498266Sopenharmony_ci option[0] = '\0'; 60613498266Sopenharmony_ci } 60713498266Sopenharmony_ci while(*p && ISBLANK(*p)) 60813498266Sopenharmony_ci p++; 60913498266Sopenharmony_ci if(*p != '=') 61013498266Sopenharmony_ci return CURLE_OK; 61113498266Sopenharmony_ci p++; 61213498266Sopenharmony_ci while(*p && ISBLANK(*p)) 61313498266Sopenharmony_ci p++; 61413498266Sopenharmony_ci if(!*p) 61513498266Sopenharmony_ci return CURLE_OK; 61613498266Sopenharmony_ci if(*p == '\"') { 61713498266Sopenharmony_ci /* quoted value */ 61813498266Sopenharmony_ci p++; 61913498266Sopenharmony_ci quoted = TRUE; 62013498266Sopenharmony_ci } 62113498266Sopenharmony_ci value_ptr = p; 62213498266Sopenharmony_ci if(quoted) { 62313498266Sopenharmony_ci while(*p && *p != '\"') 62413498266Sopenharmony_ci p++; 62513498266Sopenharmony_ci if(!*p++) 62613498266Sopenharmony_ci return CURLE_OK; 62713498266Sopenharmony_ci } 62813498266Sopenharmony_ci else { 62913498266Sopenharmony_ci while(*p && !ISBLANK(*p) && *p!= ';' && *p != ',') 63013498266Sopenharmony_ci p++; 63113498266Sopenharmony_ci } 63213498266Sopenharmony_ci num = strtoul(value_ptr, &end_ptr, 10); 63313498266Sopenharmony_ci if((end_ptr != value_ptr) && (num < ULONG_MAX)) { 63413498266Sopenharmony_ci if(strcasecompare("ma", option)) 63513498266Sopenharmony_ci maxage = num; 63613498266Sopenharmony_ci else if(strcasecompare("persist", option) && (num == 1)) 63713498266Sopenharmony_ci persist = TRUE; 63813498266Sopenharmony_ci } 63913498266Sopenharmony_ci } 64013498266Sopenharmony_ci if(dstalpnid && valid) { 64113498266Sopenharmony_ci if(!entries++) 64213498266Sopenharmony_ci /* Flush cached alternatives for this source origin, if any - when 64313498266Sopenharmony_ci this is the first entry of the line. */ 64413498266Sopenharmony_ci altsvc_flush(asi, srcalpnid, srchost, srcport); 64513498266Sopenharmony_ci 64613498266Sopenharmony_ci as = altsvc_createid(srchost, dsthost, 64713498266Sopenharmony_ci srcalpnid, dstalpnid, 64813498266Sopenharmony_ci srcport, dstport); 64913498266Sopenharmony_ci if(as) { 65013498266Sopenharmony_ci /* The expires time also needs to take the Age: value (if any) into 65113498266Sopenharmony_ci account. [See RFC 7838 section 3.1] */ 65213498266Sopenharmony_ci as->expires = maxage + time(NULL); 65313498266Sopenharmony_ci as->persist = persist; 65413498266Sopenharmony_ci Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node); 65513498266Sopenharmony_ci infof(data, "Added alt-svc: %s:%d over %s", dsthost, dstport, 65613498266Sopenharmony_ci Curl_alpnid2str(dstalpnid)); 65713498266Sopenharmony_ci } 65813498266Sopenharmony_ci } 65913498266Sopenharmony_ci } 66013498266Sopenharmony_ci else 66113498266Sopenharmony_ci break; 66213498266Sopenharmony_ci /* after the double quote there can be a comma if there's another 66313498266Sopenharmony_ci string or a semicolon if no more */ 66413498266Sopenharmony_ci if(*p == ',') { 66513498266Sopenharmony_ci /* comma means another alternative is presented */ 66613498266Sopenharmony_ci p++; 66713498266Sopenharmony_ci result = getalnum(&p, alpnbuf, sizeof(alpnbuf)); 66813498266Sopenharmony_ci if(result) 66913498266Sopenharmony_ci break; 67013498266Sopenharmony_ci } 67113498266Sopenharmony_ci } 67213498266Sopenharmony_ci else 67313498266Sopenharmony_ci break; 67413498266Sopenharmony_ci } while(*p && (*p != ';') && (*p != '\n') && (*p != '\r')); 67513498266Sopenharmony_ci 67613498266Sopenharmony_ci return CURLE_OK; 67713498266Sopenharmony_ci} 67813498266Sopenharmony_ci 67913498266Sopenharmony_ci/* 68013498266Sopenharmony_ci * Return TRUE on a match 68113498266Sopenharmony_ci */ 68213498266Sopenharmony_cibool Curl_altsvc_lookup(struct altsvcinfo *asi, 68313498266Sopenharmony_ci enum alpnid srcalpnid, const char *srchost, 68413498266Sopenharmony_ci int srcport, 68513498266Sopenharmony_ci struct altsvc **dstentry, 68613498266Sopenharmony_ci const int versions) /* one or more bits */ 68713498266Sopenharmony_ci{ 68813498266Sopenharmony_ci struct Curl_llist_element *e; 68913498266Sopenharmony_ci struct Curl_llist_element *n; 69013498266Sopenharmony_ci time_t now = time(NULL); 69113498266Sopenharmony_ci DEBUGASSERT(asi); 69213498266Sopenharmony_ci DEBUGASSERT(srchost); 69313498266Sopenharmony_ci DEBUGASSERT(dstentry); 69413498266Sopenharmony_ci 69513498266Sopenharmony_ci for(e = asi->list.head; e; e = n) { 69613498266Sopenharmony_ci struct altsvc *as = e->ptr; 69713498266Sopenharmony_ci n = e->next; 69813498266Sopenharmony_ci if(as->expires < now) { 69913498266Sopenharmony_ci /* an expired entry, remove */ 70013498266Sopenharmony_ci Curl_llist_remove(&asi->list, e, NULL); 70113498266Sopenharmony_ci altsvc_free(as); 70213498266Sopenharmony_ci continue; 70313498266Sopenharmony_ci } 70413498266Sopenharmony_ci if((as->src.alpnid == srcalpnid) && 70513498266Sopenharmony_ci hostcompare(srchost, as->src.host) && 70613498266Sopenharmony_ci (as->src.port == srcport) && 70713498266Sopenharmony_ci (versions & as->dst.alpnid)) { 70813498266Sopenharmony_ci /* match */ 70913498266Sopenharmony_ci *dstentry = as; 71013498266Sopenharmony_ci return TRUE; 71113498266Sopenharmony_ci } 71213498266Sopenharmony_ci } 71313498266Sopenharmony_ci return FALSE; 71413498266Sopenharmony_ci} 71513498266Sopenharmony_ci 71613498266Sopenharmony_ci#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_ALTSVC */ 717