summaryrefslogtreecommitdiff
path: root/Utilities/cmcurl/lib/altsvc.c
diff options
context:
space:
mode:
Diffstat (limited to 'Utilities/cmcurl/lib/altsvc.c')
-rw-r--r--Utilities/cmcurl/lib/altsvc.c297
1 files changed, 185 insertions, 112 deletions
diff --git a/Utilities/cmcurl/lib/altsvc.c b/Utilities/cmcurl/lib/altsvc.c
index 85a4e01b50..c39d86eaff 100644
--- a/Utilities/cmcurl/lib/altsvc.c
+++ b/Utilities/cmcurl/lib/altsvc.c
@@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
- * Copyright (C) 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 2019 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@@ -34,6 +34,8 @@
#include "parsedate.h"
#include "sendf.h"
#include "warnless.h"
+#include "rand.h"
+#include "rename.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@@ -48,15 +50,19 @@
#define MAX_ALTSVC_ALPNLENSTR "10"
#define MAX_ALTSVC_ALPNLEN 10
+#if (defined(USE_QUICHE) || defined(USE_NGTCP2)) && !defined(UNITTESTS)
+#define H3VERSION "h3-27"
+#else
+#define H3VERSION "h3"
+#endif
+
static enum alpnid alpn2alpnid(char *name)
{
if(strcasecompare(name, "h1"))
return ALPN_h1;
if(strcasecompare(name, "h2"))
return ALPN_h2;
- if(strcasecompare(name, "h2c"))
- return ALPN_h2c;
- if(strcasecompare(name, "h3"))
+ if(strcasecompare(name, H3VERSION))
return ALPN_h3;
return ALPN_none; /* unknown, probably rubbish input */
}
@@ -69,10 +75,8 @@ const char *Curl_alpnid2str(enum alpnid id)
return "h1";
case ALPN_h2:
return "h2";
- case ALPN_h2c:
- return "h2c";
case ALPN_h3:
- return "h3";
+ return H3VERSION;
default:
return ""; /* bad */
}
@@ -81,8 +85,8 @@ const char *Curl_alpnid2str(enum alpnid id)
static void altsvc_free(struct altsvc *as)
{
- free(as->srchost);
- free(as->dsthost);
+ free(as->src.host);
+ free(as->dst.host);
free(as);
}
@@ -97,17 +101,17 @@ static struct altsvc *altsvc_createid(const char *srchost,
if(!as)
return NULL;
- as->srchost = strdup(srchost);
- if(!as->srchost)
+ as->src.host = strdup(srchost);
+ if(!as->src.host)
goto error;
- as->dsthost = strdup(dsthost);
- if(!as->dsthost)
+ as->dst.host = strdup(dsthost);
+ if(!as->dst.host)
goto error;
- as->srcalpnid = srcalpnid;
- as->dstalpnid = dstalpnid;
- as->srcport = curlx_ultous(srcport);
- as->dstport = curlx_ultous(dstport);
+ as->src.alpnid = srcalpnid;
+ as->dst.alpnid = dstalpnid;
+ as->src.port = curlx_ultous(srcport);
+ as->dst.port = curlx_ultous(dstport);
return as;
error:
@@ -156,7 +160,7 @@ static CURLcode altsvc_add(struct altsvcinfo *asi, char *line)
date, &persist, &prio);
if(9 == rc) {
struct altsvc *as;
- time_t expires = curl_getdate(date, NULL);
+ time_t expires = Curl_getdate_capped(date);
as = altsvc_create(srchost, dsthost, srcalpn, dstalpn, srcport, dstport);
if(as) {
as->expires = expires;
@@ -183,7 +187,16 @@ static CURLcode altsvc_load(struct altsvcinfo *asi, const char *file)
{
CURLcode result = CURLE_OK;
char *line = NULL;
- FILE *fp = fopen(file, FOPEN_READTEXT);
+ FILE *fp;
+
+ /* we need a private copy of the file name so that the altsvc cache file
+ name survives an easy handle reset */
+ free(asi->filename);
+ asi->filename = strdup(file);
+ if(!asi->filename)
+ return CURLE_OUT_OF_MEMORY;
+
+ fp = fopen(file, FOPEN_READTEXT);
if(fp) {
line = malloc(MAX_ALTSVC_LINE);
if(!line)
@@ -204,6 +217,7 @@ static CURLcode altsvc_load(struct altsvcinfo *asi, const char *file)
return result;
fail:
+ Curl_safefree(asi->filename);
free(line);
fclose(fp);
return CURLE_OUT_OF_MEMORY;
@@ -226,8 +240,8 @@ static CURLcode altsvc_out(struct altsvc *as, FILE *fp)
"\"%d%02d%02d "
"%02d:%02d:%02d\" "
"%u %d\n",
- Curl_alpnid2str(as->srcalpnid), as->srchost, as->srcport,
- Curl_alpnid2str(as->dstalpnid), as->dsthost, as->dstport,
+ Curl_alpnid2str(as->src.alpnid), as->src.host, as->src.port,
+ Curl_alpnid2str(as->dst.alpnid), as->dst.host, as->dst.port,
stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday,
stamp.tm_hour, stamp.tm_min, stamp.tm_sec,
as->persist, as->prio);
@@ -252,7 +266,7 @@ struct altsvcinfo *Curl_altsvc_init(void)
#ifdef USE_NGHTTP2
| CURLALTSVC_H2
#endif
-#ifdef USE_HTTP3
+#ifdef ENABLE_QUIC
| CURLALTSVC_H3
#endif
;
@@ -297,6 +311,7 @@ void Curl_altsvc_cleanup(struct altsvcinfo *altsvc)
n = e->next;
altsvc_free(as);
}
+ free(altsvc->filename);
free(altsvc);
}
}
@@ -304,34 +319,57 @@ void Curl_altsvc_cleanup(struct altsvcinfo *altsvc)
/*
* Curl_altsvc_save() writes the altsvc cache to a file.
*/
-CURLcode Curl_altsvc_save(struct altsvcinfo *altsvc, const char *file)
+CURLcode Curl_altsvc_save(struct Curl_easy *data,
+ struct altsvcinfo *altsvc, const char *file)
{
struct curl_llist_element *e;
struct curl_llist_element *n;
CURLcode result = CURLE_OK;
FILE *out;
+ char *tempstore;
+ unsigned char randsuffix[9];
if(!altsvc)
/* no cache activated */
return CURLE_OK;
- if((altsvc->flags & CURLALTSVC_READONLYFILE) || !file[0])
- /* marked as read-only or zero length file name */
+ /* if not new name is given, use the one we stored from the load */
+ if(!file && altsvc->filename)
+ file = altsvc->filename;
+
+ if((altsvc->flags & CURLALTSVC_READONLYFILE) || !file || !file[0])
+ /* marked as read-only, no file or zero length file name */
return CURLE_OK;
- out = fopen(file, FOPEN_WRITETEXT);
+
+ if(Curl_rand_hex(data, randsuffix, sizeof(randsuffix)))
+ return CURLE_FAILED_INIT;
+
+ tempstore = aprintf("%s.%s.tmp", file, randsuffix);
+ if(!tempstore)
+ return CURLE_OUT_OF_MEMORY;
+
+ out = fopen(tempstore, FOPEN_WRITETEXT);
if(!out)
- return CURLE_WRITE_ERROR;
- fputs("# Your alt-svc cache. https://curl.haxx.se/docs/alt-svc.html\n"
- "# This file was generated by libcurl! Edit at your own risk.\n",
- out);
- for(e = altsvc->list.head; e; e = n) {
- struct altsvc *as = e->ptr;
- n = e->next;
- result = altsvc_out(as, out);
+ result = CURLE_WRITE_ERROR;
+ else {
+ fputs("# Your alt-svc cache. https://curl.haxx.se/docs/alt-svc.html\n"
+ "# This file was generated by libcurl! Edit at your own risk.\n",
+ out);
+ for(e = altsvc->list.head; e; e = n) {
+ struct altsvc *as = e->ptr;
+ n = e->next;
+ result = altsvc_out(as, out);
+ if(result)
+ break;
+ }
+ fclose(out);
+ if(!result && Curl_rename(tempstore, file))
+ result = CURLE_WRITE_ERROR;
+
if(result)
- break;
+ unlink(tempstore);
}
- fclose(out);
+ free(tempstore);
return result;
}
@@ -343,15 +381,15 @@ static CURLcode getalnum(const char **ptr, char *alpnbuf, size_t buflen)
while(*p && ISBLANK(*p))
p++;
protop = p;
- while(*p && ISALNUM(*p))
+ while(*p && !ISBLANK(*p) && (*p != ';') && (*p != '='))
p++;
len = p - protop;
+ *ptr = p;
if(!len || (len >= buflen))
return CURLE_BAD_FUNCTION_ARGUMENT;
memcpy(alpnbuf, protop, len);
alpnbuf[len] = 0;
- *ptr = p;
return CURLE_OK;
}
@@ -365,9 +403,9 @@ static void altsvc_flush(struct altsvcinfo *asi, enum alpnid srcalpnid,
for(e = asi->list.head; e; e = n) {
struct altsvc *as = e->ptr;
n = e->next;
- if((srcalpnid == as->srcalpnid) &&
- (srcport == as->srcport) &&
- strcasecompare(srchost, as->srchost)) {
+ if((srcalpnid == as->src.alpnid) &&
+ (srcport == as->src.port) &&
+ strcasecompare(srchost, as->src.host)) {
Curl_llist_remove(&asi->list, e, NULL);
altsvc_free(as);
asi->num--;
@@ -397,6 +435,10 @@ static time_t debugtime(void *unused)
*
* 'value' points to the header *value*. That's contents to the right of the
* header name.
+ *
+ * Currently this function rejects invalid data without returning an error.
+ * Invalid host name, port number will result in the specific alternative
+ * being rejected. Unknown protocols are skipped.
*/
CURLcode Curl_altsvc_parse(struct Curl_easy *data,
struct altsvcinfo *asi, const char *value,
@@ -410,12 +452,11 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data,
char alpnbuf[MAX_ALTSVC_ALPNLEN] = "";
struct altsvc *as;
unsigned short dstport = srcport; /* the same by default */
- const char *semip;
- time_t maxage = 24 * 3600; /* default is 24 hours */
- bool persist = FALSE;
CURLcode result = getalnum(&p, alpnbuf, sizeof(alpnbuf));
- if(result)
- return result;
+ if(result) {
+ infof(data, "Excessive alt-svc header, ignoring...\n");
+ return CURLE_OK;
+ }
DEBUGASSERT(asi);
@@ -427,47 +468,20 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data,
return CURLE_OK;
}
- /* The 'ma' and 'persist' flags are annoyingly meant for all alternatives
- but are set after the list on the line. Scan for the semicolons and get
- those fields first! */
- semip = p;
- do {
- semip = strchr(semip, ';');
- if(semip) {
- char option[32];
- unsigned long num;
- char *end_ptr;
- semip++; /* pass the semicolon */
- result = getalnum(&semip, option, sizeof(option));
- if(result)
- break;
- while(*semip && ISBLANK(*semip))
- semip++;
- if(*semip != '=')
- continue;
- semip++;
- num = strtoul(semip, &end_ptr, 10);
- if(num < ULONG_MAX) {
- if(strcasecompare("ma", option))
- maxage = num;
- else if(strcasecompare("persist", option) && (num == 1))
- persist = TRUE;
- }
- semip = end_ptr;
- }
- } while(semip);
-
do {
if(*p == '=') {
/* [protocol]="[host][:port]" */
dstalpnid = alpn2alpnid(alpnbuf);
- if(!dstalpnid) {
- infof(data, "Unknown alt-svc protocol \"%s\", ignoring...\n", alpnbuf);
- return CURLE_OK;
- }
p++;
if(*p == '\"') {
const char *dsthost;
+ const char *value_ptr;
+ char option[32];
+ unsigned long num;
+ char *end_ptr;
+ bool quoted = FALSE;
+ time_t maxage = 24 * 3600; /* default is 24 hours */
+ bool persist = FALSE;
p++;
if(*p != ':') {
/* host name starts here */
@@ -475,11 +489,15 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data,
while(*p && (ISALNUM(*p) || (*p == '.') || (*p == '-')))
p++;
len = p - hostp;
- if(!len || (len >= MAX_ALTSVC_HOSTLEN))
- return CURLE_BAD_FUNCTION_ARGUMENT;
- memcpy(namebuf, hostp, len);
- namebuf[len] = 0;
- dsthost = namebuf;
+ if(!len || (len >= MAX_ALTSVC_HOSTLEN)) {
+ infof(data, "Excessive alt-svc host name, ignoring...\n");
+ dstalpnid = ALPN_none;
+ }
+ else {
+ memcpy(namebuf, hostp, len);
+ namebuf[len] = 0;
+ dsthost = namebuf;
+ }
}
else {
/* no destination name, use source host */
@@ -487,31 +505,86 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data,
}
if(*p == ':') {
/* a port number */
- char *end_ptr;
unsigned long port = strtoul(++p, &end_ptr, 10);
if(port > USHRT_MAX || end_ptr == p || *end_ptr != '\"') {
infof(data, "Unknown alt-svc port number, ignoring...\n");
- return CURLE_OK;
+ dstalpnid = ALPN_none;
}
p = end_ptr;
dstport = curlx_ultous(port);
}
if(*p++ != '\"')
- return CURLE_BAD_FUNCTION_ARGUMENT;
- as = altsvc_createid(srchost, dsthost,
- srcalpnid, dstalpnid,
- srcport, dstport);
- if(as) {
- /* The expires time also needs to take the Age: value (if any) into
- account. [See RFC 7838 section 3.1] */
- as->expires = maxage + time(NULL);
- as->persist = persist;
- Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node);
- asi->num++; /* one more entry */
- infof(data, "Added alt-svc: %s:%d over %s\n", dsthost, dstport,
- Curl_alpnid2str(dstalpnid));
+ break;
+ /* Handle the optional 'ma' and 'persist' flags. Unknown flags
+ are skipped. */
+ for(;;) {
+ while(*p && ISBLANK(*p) && *p != ';' && *p != ',')
+ p++;
+ if(!*p || *p == ',')
+ break;
+ p++; /* pass the semicolon */
+ if(!*p)
+ break;
+ result = getalnum(&p, option, sizeof(option));
+ if(result) {
+ /* skip option if name is too long */
+ option[0] = '\0';
+ }
+ while(*p && ISBLANK(*p))
+ p++;
+ if(*p != '=')
+ return CURLE_OK;
+ p++;
+ while(*p && ISBLANK(*p))
+ p++;
+ if(!*p)
+ return CURLE_OK;
+ if(*p == '\"') {
+ /* quoted value */
+ p++;
+ quoted = TRUE;
+ }
+ value_ptr = p;
+ if(quoted) {
+ while(*p && *p != '\"')
+ p++;
+ if(!*p++)
+ return CURLE_OK;
+ }
+ else {
+ while(*p && !ISBLANK(*p) && *p!= ';' && *p != ',')
+ p++;
+ }
+ num = strtoul(value_ptr, &end_ptr, 10);
+ if((end_ptr != value_ptr) && (num < ULONG_MAX)) {
+ if(strcasecompare("ma", option))
+ maxage = num;
+ else if(strcasecompare("persist", option) && (num == 1))
+ persist = TRUE;
+ }
+ }
+ if(dstalpnid) {
+ as = altsvc_createid(srchost, dsthost,
+ srcalpnid, dstalpnid,
+ srcport, dstport);
+ if(as) {
+ /* The expires time also needs to take the Age: value (if any) into
+ account. [See RFC 7838 section 3.1] */
+ as->expires = maxage + time(NULL);
+ as->persist = persist;
+ Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node);
+ asi->num++; /* one more entry */
+ infof(data, "Added alt-svc: %s:%d over %s\n", dsthost, dstport,
+ Curl_alpnid2str(dstalpnid));
+ }
+ }
+ else {
+ infof(data, "Unknown alt-svc protocol \"%s\", skipping...\n",
+ alpnbuf);
}
}
+ else
+ break;
/* after the double quote there can be a comma if there's another
string or a semicolon if no more */
if(*p == ',') {
@@ -519,11 +592,11 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data,
p++;
result = getalnum(&p, alpnbuf, sizeof(alpnbuf));
if(result)
- /* failed to parse, but since we already did at least one host we
- return OK */
- return CURLE_OK;
+ break;
}
}
+ else
+ break;
} while(*p && (*p != ';') && (*p != '\n') && (*p != '\r'));
return CURLE_OK;
@@ -535,31 +608,31 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data,
bool Curl_altsvc_lookup(struct altsvcinfo *asi,
enum alpnid srcalpnid, const char *srchost,
int srcport,
- enum alpnid *dstalpnid, const char **dsthost,
- int *dstport)
+ struct altsvc **dstentry,
+ const int versions) /* one or more bits */
{
struct curl_llist_element *e;
struct curl_llist_element *n;
time_t now = time(NULL);
DEBUGASSERT(asi);
DEBUGASSERT(srchost);
- DEBUGASSERT(dsthost);
+ DEBUGASSERT(dstentry);
for(e = asi->list.head; e; e = n) {
struct altsvc *as = e->ptr;
n = e->next;
if(as->expires < now) {
/* an expired entry, remove */
+ Curl_llist_remove(&asi->list, e, NULL);
altsvc_free(as);
continue;
}
- if((as->srcalpnid == srcalpnid) &&
- strcasecompare(as->srchost, srchost) &&
- as->srcport == srcport) {
+ if((as->src.alpnid == srcalpnid) &&
+ strcasecompare(as->src.host, srchost) &&
+ (as->src.port == srcport) &&
+ (versions & as->dst.alpnid)) {
/* match */
- *dstalpnid = as->dstalpnid;
- *dsthost = as->dsthost;
- *dstport = as->dstport;
+ *dstentry = as;
return TRUE;
}
}