diff --git a/docs/ALTSVC.md b/docs/ALTSVC.md
index 560b43748a..b9117e4d46 100644
--- a/docs/ALTSVC.md
+++ b/docs/ALTSVC.md
@@ -33,6 +33,9 @@ space separated fields.
8. Boolean (1 or 0) if "persist" was set for this entry
9. Integer priority value (not currently used)
+If the host name is an IPv6 numerical address, it is stored with brackets such
+as `[::1]`.
+
# TODO
- handle multiple response headers, when one of them says `clear` (should
diff --git a/lib/altsvc.c b/lib/altsvc.c
index 11009d5ac8..22b0b69c77 100644
--- a/lib/altsvc.c
+++ b/lib/altsvc.c
@@ -38,6 +38,8 @@
#include "warnless.h"
#include "fopen.h"
#include "rename.h"
+#include "strdup.h"
+#include "inet_pton.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@@ -97,19 +99,39 @@ static struct altsvc *altsvc_createid(const char *srchost,
{
struct altsvc *as = calloc(sizeof(struct altsvc), 1);
size_t hlen;
+ size_t dlen;
if(!as)
return NULL;
hlen = strlen(srchost);
+ dlen = strlen(dsthost);
DEBUGASSERT(hlen);
- as->src.host = strdup(srchost);
+ DEBUGASSERT(dlen);
+ if(!hlen || !dlen)
+ /* bad input */
+ return NULL;
+ if((hlen > 2) && srchost[0] == '[') {
+ /* IPv6 address, strip off brackets */
+ srchost++;
+ hlen -= 2;
+ }
+ else if(srchost[hlen - 1] == '.')
+ /* strip off trailing dot */
+ hlen--;
+ if((dlen > 2) && dsthost[0] == '[') {
+ /* IPv6 address, strip off brackets */
+ dsthost++;
+ dlen -= 2;
+ }
+
+ as->src.host = Curl_memdup(srchost, hlen + 1);
if(!as->src.host)
goto error;
- if(hlen && (srchost[hlen - 1] == '.'))
- /* strip off trailing any dot */
- as->src.host[--hlen] = 0;
- as->dst.host = strdup(dsthost);
+ as->src.host[hlen] = 0;
+
+ as->dst.host = Curl_memdup(dsthost, dlen + 1);
if(!as->dst.host)
goto error;
+ as->dst.host[dlen] = 0;
as->src.alpnid = srcalpnid;
as->dst.alpnid = dstalpnid;
@@ -231,18 +253,40 @@ fail:
static CURLcode altsvc_out(struct altsvc *as, FILE *fp)
{
struct tm stamp;
+ const char *dst6_pre = "";
+ const char *dst6_post = "";
+ const char *src6_pre = "";
+ const char *src6_post = "";
CURLcode result = Curl_gmtime(as->expires, &stamp);
if(result)
return result;
-
+#ifdef ENABLE_IPV6
+ else {
+ char ipv6_unused[16];
+ if(1 == Curl_inet_pton(AF_INET6, as->dst.host, ipv6_unused)) {
+ dst6_pre = "[";
+ dst6_post = "]";
+ }
+ if(1 == Curl_inet_pton(AF_INET6, as->src.host, ipv6_unused)) {
+ src6_pre = "[";
+ src6_post = "]";
+ }
+ }
+#endif
fprintf(fp,
- "%s %s %u "
- "%s %s %u "
+ "%s %s%s%s %u "
+ "%s %s%s%s %u "
"\"%d%02d%02d "
"%02d:%02d:%02d\" "
"%u %d\n",
- Curl_alpnid2str(as->src.alpnid), as->src.host, as->src.port,
- Curl_alpnid2str(as->dst.alpnid), as->dst.host, as->dst.port,
+ Curl_alpnid2str(as->src.alpnid),
+ src6_pre, as->src.host, src6_post,
+ as->src.port,
+
+ Curl_alpnid2str(as->dst.alpnid),
+ dst6_pre, as->dst.host, dst6_post,
+ 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);
@@ -500,9 +544,21 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data,
if(*p != ':') {
/* host name starts here */
const char *hostp = p;
- while(*p && (ISALNUM(*p) || (*p == '.') || (*p == '-')))
- p++;
- len = p - hostp;
+ if(*p == '[') {
+ /* pass all valid IPv6 letters - does not handle zone id */
+ len = strspn(++p, "0123456789abcdefABCDEF:.");
+ if(p[len] != ']')
+ /* invalid host syntax, bail out */
+ break;
+ /* we store the IPv6 numerical address *with* brackets */
+ len += 2;
+ p = &p[len-1];
+ }
+ else {
+ while(*p && (ISALNUM(*p) || (*p == '.') || (*p == '-')))
+ p++;
+ len = p - hostp;
+ }
if(!len || (len >= MAX_ALTSVC_HOSTLEN)) {
infof(data, "Excessive alt-svc host name, ignoring.");
valid = FALSE;
diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc
index 78edff66c4..a0015b3b20 100644
--- a/tests/data/Makefile.inc
+++ b/tests/data/Makefile.inc
@@ -70,7 +70,7 @@ test399 test400 test401 test402 test403 test404 test405 test406 test407 \
test408 test409 test410 test411 test412 test413 test414 test415 test416 \
test417 test418 test419 test420 test421 test422 test423 test424 test425 \
test426 test427 test428 test429 test430 test431 test432 test433 test434 \
-test435 test436 \
+test435 test436 test437 test438 \
\
test440 test441 test442 test443 test444 test445 test446 test447 test448 \
test449 test450 test451 test452 test453 test454 test455 test456 \
diff --git a/tests/data/test437 b/tests/data/test437
new file mode 100644
index 0000000000..a49fb8c48b
--- /dev/null
+++ b/tests/data/test437
@@ -0,0 +1,68 @@
+
+
+
+HTTP
+Alt-Svc
+
+
+
+#
+# Server-side
+
+
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Content-Length: 6
+Connection: close
+Content-Type: text/html
+Funny-head: yesyes
+Alt-Svc: h1="[ffff::1]:8181"
+
+-foo-
+
+
+
+#
+# Client-side
+
+
+debug
+alt-svc
+
+
+http
+
+
+Alt-Svc to numerical IPv6 address
+
+
+# make debug-curl accept Alt-Svc over plain HTTP
+CURL_ALTSVC_HTTP="yeah"
+
+
+http://%HOSTIP:%HTTPPORT/%TESTNUMBER --alt-svc "%LOGDIR/altsvc-%TESTNUMBER"
+
+
+
+#
+# Verify data after the test has been "shot"
+
+
+GET /%TESTNUMBER HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+User-Agent: curl/%VERSION
+Accept: */*
+
+
+
+# strip out the (dynamic) expire date from the file so that the rest
+# matches
+s/\"([^\"]*)\"/TIMESTAMP/
+
+
+# Your alt-svc cache. https://curl.se/docs/alt-svc.html
+# This file was generated by libcurl! Edit at your own risk.
+h1 %HOSTIP %HTTPPORT h1 [ffff::1] 8181 TIMESTAMP 0 0
+
+
+
diff --git a/tests/data/test438 b/tests/data/test438
new file mode 100644
index 0000000000..9015e927e8
--- /dev/null
+++ b/tests/data/test438
@@ -0,0 +1,90 @@
+
+
+
+HTTP
+HTTP GET
+Alt-Svc
+HTTP/2
+
+
+
+#
+# Server-side
+
+
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Content-Length: 6
+Connection: close
+Content-Type: text/html
+Funny-Head: yesyes
+Alt-Svc: h1="%HOST6IP:%HTTP6PORT", ma=315360000; persist=0
+
+-foo-
+
+
+
+#
+# Client-side
+
+
+alt-svc
+debug
+ipv6
+
+
+http
+http-ipv6
+
+
+HTTPS IPv4 GET translated by alt-svc to IPv6 address
+
+
+# make debug-curl accept Alt-Svc over plain HTTP
+CURL_ALTSVC_HTTP="yeah"
+
+
+--alt-svc "%LOGDIR/altsvc-%TESTNUMBER" "http://%HOSTIP:%HTTPPORT/%TESTNUMBER" "http://%HOSTIP:%HTTPPORT/%TESTNUMBER"
+
+
+h1 %HOSTIP %HTTPPORT h1 %HOST6IP %HTTP6PORT "20290222 22:19:28" 0 0
+
+
+
+
+#
+# Verify data after the test has been "shot"
+
+
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Content-Length: 6
+Connection: close
+Content-Type: text/html
+Funny-Head: yesyes
+Alt-Svc: h1="%HOST6IP:%HTTP6PORT", ma=315360000; persist=0
+
+-foo-
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Content-Length: 6
+Connection: close
+Content-Type: text/html
+Funny-Head: yesyes
+Alt-Svc: h1="%HOST6IP:%HTTP6PORT", ma=315360000; persist=0
+
+-foo-
+
+
+s/^server: nghttpx.*\r?\n//
+# strip out the (dynamic) expire date from the file so that the rest
+# matches
+s/\"2([^\"]*)\"/TIMESTAMP/
+
+
+# Your alt-svc cache. https://curl.se/docs/alt-svc.html
+# This file was generated by libcurl! Edit at your own risk.
+h1 %HOSTIP %HTTPPORT h1 %HOST6IP %HTTP6PORT TIMESTAMP 0 0
+
+
+