summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Stenberg <daniel@haxx.se>2019-03-03 11:17:52 +0100
committerDaniel Stenberg <daniel@haxx.se>2019-03-03 11:17:52 +0100
commite1be8254534898fccafc5d6cd04f6235f283cfbd (patch)
treed48bbbf5cb59d93e82fc14f13f1b0c7a7e5044d3
parent4331a3b8fa40cc8d71b7abb36b096dccdc11e3cb (diff)
downloadcurl-e1be8254534898fccafc5d6cd04f6235f283cfbd.tar.gz
alt-svc: the libcurl bits
-rwxr-xr-xconfigure.ac76
-rw-r--r--docs/libcurl/curl_easy_setopt.36
-rw-r--r--docs/libcurl/opts/CURLOPT_ALTSVC.361
-rw-r--r--docs/libcurl/opts/CURLOPT_ALTSVC_CTRL.392
-rw-r--r--docs/libcurl/opts/Makefile.inc2
-rw-r--r--docs/libcurl/symbols-in-versions17
-rw-r--r--include/curl/curl.h15
-rw-r--r--include/curl/typecheck-gcc.h3
-rw-r--r--lib/Makefile.inc5
-rw-r--r--lib/altsvc.c569
-rw-r--r--lib/altsvc.h77
-rw-r--r--lib/cookie.c4
-rw-r--r--lib/cookie.h1
-rw-r--r--lib/http.c15
-rw-r--r--lib/setopt.c26
-rw-r--r--lib/url.c34
-rw-r--r--lib/urldata.h6
-rw-r--r--lib/version.c3
-rw-r--r--src/tool_help.c1
-rw-r--r--tests/FILEFORMAT1
-rw-r--r--tests/data/Makefile.inc2
-rw-r--r--tests/data/test165457
-rwxr-xr-xtests/runtests.pl10
-rw-r--r--tests/unit/Makefile.inc5
-rw-r--r--tests/unit/unit1654.c124
25 files changed, 1180 insertions, 32 deletions
diff --git a/configure.ac b/configure.ac
index bb1a68508..3707a886e 100755
--- a/configure.ac
+++ b/configure.ac
@@ -2696,7 +2696,7 @@ AC_ARG_WITH(libpsl,
with_libpsl=yes)
if test $with_libpsl != "no"; then
AC_SEARCH_LIBS(psl_builtin, psl,
- [curl_psl_msg="yes";
+ [curl_psl_msg="enabled";
AC_DEFINE([USE_LIBPSL], [1], [PSL support enabled])
],
[curl_psl_msg="no (libpsl not found)";
@@ -2704,7 +2704,7 @@ if test $with_libpsl != "no"; then
]
)
fi
-AM_CONDITIONAL([USE_LIBPSL], [test "$curl_psl_msg" = "yes"])
+AM_CONDITIONAL([USE_LIBPSL], [test "$curl_psl_msg" = "enabled"])
dnl **********************************************************************
dnl Check for libmetalink
@@ -4063,6 +4063,32 @@ AC_HELP_STRING([--disable-cookies],[Disable cookies support]),
)
dnl ************************************************************
+dnl switch on/off alt-svc
+dnl
+curl_altsvc_msg="no (--enable-alt-svc)";
+AC_MSG_CHECKING([whether to support alt-svc])
+AC_ARG_ENABLE(alt-svc,
+AC_HELP_STRING([--enable-alt-svc],[Enable alt-svc support])
+AC_HELP_STRING([--disable-alt-svc],[Disable alt-svc support]),
+[ case "$enableval" in
+ no)
+ AC_MSG_RESULT(no)
+ ;;
+ *) AC_MSG_RESULT(yes)
+ curl_altsvc_msg="enabled";
+ enable_altsvc="yes"
+ experimental="alt-svc"
+ ;;
+ esac ],
+ AC_MSG_RESULT(no)
+)
+
+if test "$enable_altsvc" = "yes"; then
+ AC_DEFINE(USE_ALTSVC, 1, [to enable alt-svc])
+ experimental="alt-svc"
+fi
+
+dnl ************************************************************
dnl hiding of library internal symbols
dnl
CURL_CONFIGURE_SYMBOL_HIDING
@@ -4131,10 +4157,14 @@ if test "x$HAVE_GSSAPI" = "x1"; then
SUPPORT_FEATURES="$SUPPORT_FEATURES GSS-API"
fi
-if test "x$curl_psl_msg" = "xyes"; then
+if test "x$curl_psl_msg" = "xenabled"; then
SUPPORT_FEATURES="$SUPPORT_FEATURES PSL"
fi
+if test "x$enable_altsvc" = "xyes"; then
+ SUPPORT_FEATURES="$SUPPORT_FEATURES alt-svc"
+fi
+
if test "x$CURL_DISABLE_CRYPTO_AUTH" != "x1" -a \
\( "x$HAVE_GSSAPI" = "x1" -o "x$USE_WINDOWS_SSPI" = "x1" \); then
SUPPORT_FEATURES="$SUPPORT_FEATURES SPNEGO"
@@ -4330,32 +4360,38 @@ AC_MSG_NOTICE([Configured to build curl/libcurl:
LIBS: ${LIBS}
curl version: ${CURLVERSION}
- SSL support: ${curl_ssl_msg}
- SSH support: ${curl_ssh_msg}
- zlib support: ${curl_zlib_msg}
- brotli support: ${curl_brotli_msg}
- GSS-API support: ${curl_gss_msg}
- TLS-SRP support: ${curl_tls_srp_msg}
+ SSL: ${curl_ssl_msg}
+ SSH: ${curl_ssh_msg}
+ zlib: ${curl_zlib_msg}
+ brotli: ${curl_brotli_msg}
+ GSS-API: ${curl_gss_msg}
+ TLS-SRP: ${curl_tls_srp_msg}
resolver: ${curl_res_msg}
- IPv6 support: ${curl_ipv6_msg}
- Unix sockets support: ${curl_unix_sockets_msg}
- IDN support: ${curl_idn_msg}
+ IPv6: ${curl_ipv6_msg}
+ Unix sockets: ${curl_unix_sockets_msg}
+ IDN: ${curl_idn_msg}
Build libcurl: Shared=${enable_shared}, Static=${enable_static}
Built-in manual: ${curl_manual_msg}
--libcurl option: ${curl_libcurl_msg}
Verbose errors: ${curl_verbose_msg}
Code coverage: ${curl_coverage_msg}
- SSPI support: ${curl_sspi_msg}
+ SSPI: ${curl_sspi_msg}
ca cert bundle: ${ca}${ca_warning}
ca cert path: ${capath}${capath_warning}
ca fallback: ${with_ca_fallback}
- LDAP support: ${curl_ldap_msg}
- LDAPS support: ${curl_ldaps_msg}
- RTSP support: ${curl_rtsp_msg}
- RTMP support: ${curl_rtmp_msg}
- metalink support: ${curl_mtlnk_msg}
- PSL support: ${curl_psl_msg}
- HTTP2 support: ${curl_h2_msg}
+ LDAP: ${curl_ldap_msg}
+ LDAPS: ${curl_ldaps_msg}
+ RTSP: ${curl_rtsp_msg}
+ RTMP: ${curl_rtmp_msg}
+ Metalink: ${curl_mtlnk_msg}
+ PSL: ${curl_psl_msg}
+ Alt-svc: ${curl_altsvc_msg}
+ HTTP2: ${curl_h2_msg}
Protocols: ${SUPPORT_PROTOCOLS}
Features: ${SUPPORT_FEATURES}
])
+if test -n "$experimental"; then
+ cat >&2 << _EOF
+ WARNING: $experimental is enabled but marked EXPERIMENTAL. Use with caution!
+_EOF
+fi
diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3
index 6d63912d7..fc361d80c 100644
--- a/docs/libcurl/curl_easy_setopt.3
+++ b/docs/libcurl/curl_easy_setopt.3
@@ -5,7 +5,7 @@
.\" * | (__| |_| | _ <| |___
.\" * \___|\___/|_| \_\_____|
.\" *
-.\" * Copyright (C) 1998 - 2018, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" * Copyright (C) 1998 - 2019, 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
@@ -313,6 +313,10 @@ File to write cookies to. See \fICURLOPT_COOKIEJAR(3)\fP
Start a new cookie session. See \fICURLOPT_COOKIESESSION(3)\fP
.IP CURLOPT_COOKIELIST
Add or control cookies. See \fICURLOPT_COOKIELIST(3)\fP
+.IP CURLOPT_ALTSVC
+Specify the Alt-Svc: cache file name. See \fICURLOPT_ALTSVC(3)\fP
+.IP CURLOPT_ALTSVC_CTRL
+Enable and configure Alt-Svc: treatment. See \fICURLOPT_ALTSVC_CTRL(3)\fP
.IP CURLOPT_HTTPGET
Do an HTTP GET request. See \fICURLOPT_HTTPGET(3)\fP
.IP CURLOPT_REQUEST_TARGET
diff --git a/docs/libcurl/opts/CURLOPT_ALTSVC.3 b/docs/libcurl/opts/CURLOPT_ALTSVC.3
new file mode 100644
index 000000000..d6b5d87f8
--- /dev/null
+++ b/docs/libcurl/opts/CURLOPT_ALTSVC.3
@@ -0,0 +1,61 @@
+.\" **************************************************************************
+.\" * _ _ ____ _
+.\" * Project ___| | | | _ \| |
+.\" * / __| | | | |_) | |
+.\" * | (__| |_| | _ <| |___
+.\" * \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2019, 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
+.\" * are also available at https://curl.haxx.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" **************************************************************************
+.\"
+.TH CURLOPT_ALTSVC 3 "5 Feb 2019" "libcurl 7.64.1" "curl_easy_setopt options"
+.SH NAME
+CURLOPT_ALTSVC \- set alt-svc cache file name
+.SH SYNOPSIS
+.nf
+#include <curl/curl.h>
+
+CURLcode curl_easy_setopt(CURL *handle, CURLOPT_ALTSVC, char *filename);
+.fi
+.SH EXPERIMENTAL
+Warning: this feature is early code and is marked as experimental. It can only
+be enabled by explictly telling configure with \fB--enable-alt-svc\fP. You are
+advised to not ship this in production before the experimental label is
+removed.
+.SH DESCRIPTION
+Pass in a pointer to a \fIfilename\fP to instruct libcurl to use that file as
+the Alt-Svc cache to read existing cache contents from and possibly also write
+it back to a after a transfer, unless \fBCURLALTSVC_READONLYFILE\fP is set in
+\fICURLOPT_ALTSVC_CTRL(3)\fP.
+.SH DEFAULT
+NULL. The alt-svc cache is not read nor written to file.
+.SH PROTOCOLS
+HTTPS
+.SH EXAMPLE
+.nf
+CURL *curl = curl_easy_init();
+if(curl) {
+ curl_easy_setopt(curl, CURLOPT_ALTSVC_CTRL, CURLALTSVC_H1);
+ curl_easy_setopt(curl, CURLOPT_ALTSVC, "altsvc-cache.txt");
+ curl_easy_perform(curl);
+}
+.fi
+.SH AVAILABILITY
+Added in 7.64.1
+.SH RETURN VALUE
+Returns CURLE_OK if the option is supported, and CURLE_UNKNOWN_OPTION if not.
+.SH "SEE ALSO"
+.BR CURLOPT_ALTSVC_CTRL "(3), " CURLOPT_CONNECT_TO "(3), " CURLOPT_RESOLVE "(3), "
+.BR CURLOPT_COOKIEFILE "(3), "
diff --git a/docs/libcurl/opts/CURLOPT_ALTSVC_CTRL.3 b/docs/libcurl/opts/CURLOPT_ALTSVC_CTRL.3
new file mode 100644
index 000000000..bdbb382a3
--- /dev/null
+++ b/docs/libcurl/opts/CURLOPT_ALTSVC_CTRL.3
@@ -0,0 +1,92 @@
+.\" **************************************************************************
+.\" * _ _ ____ _
+.\" * Project ___| | | | _ \| |
+.\" * / __| | | | |_) | |
+.\" * | (__| |_| | _ <| |___
+.\" * \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2019, 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
+.\" * are also available at https://curl.haxx.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" **************************************************************************
+.\"
+.TH CURLOPT_ALTSVC_CTRL 3 "5 Feb 2019" "libcurl 7.64.1" "curl_easy_setopt options"
+.SH NAME
+CURLOPT_ALTSVC_CTRL \- control alt-svc behavior
+.SH SYNOPSIS
+.nf
+#include <curl/curl.h>
+
+#define CURLALTSVC_IMMEDIATELY (1<<0)
+#define CURLALTSVC_ALTUSED (1<<1)
+#define CURLALTSVC_READONLYFILE (1<<2)
+#define CURLALTSVC_H1 (1<<3)
+#define CURLALTSVC_H2 (1<<4)
+#define CURLALTSVC_H3 (1<<5)
+
+CURLcode curl_easy_setopt(CURL *handle, CURLOPT_ALTSVC_CTRL, long bitmask);
+.fi
+.SH EXPERIMENTAL
+Warning: this feature is early code and is marked as experimental. It can only
+be enabled by explictly telling configure with \fB--enable-alt-svc\fP. You are
+advised to not ship this in production before the experimental label is
+removed.
+.SH DESCRIPTION
+Populate the long \fIbitmask\fP with the correct set of features to instruct
+libcurl how to handle Alt-Svc for the transfers using this handle.
+
+libcurl will only accept Alt-Svc headers over a secure transport, meaning
+HTTPS. It will also only complete a request to an alternative origin if that
+origin is properly hosted over HTTPS. These requirements are there to make
+sure both the source and the destination are legitimate.
+
+Setting any bit will enable the alt-svc engine.
+.IP "CURLALTSVC_IMMEDIATELY"
+If an Alt-Svc: header is received, this instructs libcurl to switch to one of
+those alternatives asap rather than to save it and use for the next request.
+.IP "CURLALTSVC_ALTUSED"
+Issue the Alt-Used: header in all requests that have been redirected by
+alt-svc.
+.IP "CURLALTSVC_READONLYFILE"
+Do not write the alt-svc cache back to the file specified with
+\fICURLOPT_ALTSVC(3)\fP even if it gets updated. By default a file specified
+with that option will be read and written to as deemed necessary.
+.IP "CURLALTSVC_H1"
+Accept alternative services offered over HTTP/1.1.
+.IP "CURLALTSVC_H2"
+Accept alternative services offered over HTTP/2. This will only be used if
+libcurl was also built to actually support HTTP/2, otherwise this bit will be
+ignored.
+.IP "CURLALTSVC_H3"
+Accept alternative services offered over HTTP/3. This will only be used if
+libcurl was also built to actually support HTTP/3, otherwise this bit will be
+ignored.
+.SH DEFAULT
+0. No Alt-Svc treatment.
+.SH PROTOCOLS
+HTTPS
+.SH EXAMPLE
+.nf
+CURL *curl = curl_easy_init();
+if(curl) {
+ curl_easy_setopt(curl, CURLOPT_ALTSVC_CTRL, CURLALTSVC_H1);
+ curl_easy_setopt(curl, CURLOPT_ALTSVC, "altsvc-cache.txt");
+ curl_easy_perform(curl);
+}
+.fi
+.SH AVAILABILITY
+Added in 7.64.1
+.SH RETURN VALUE
+Returns CURLE_OK if the option is supported, and CURLE_UNKNOWN_OPTION if not.
+.SH "SEE ALSO"
+.BR CURLOPT_ALTSVC "(3), " CURLOPT_CONNECT_TO "(3), " CURLOPT_RESOLVE "(3), "
diff --git a/docs/libcurl/opts/Makefile.inc b/docs/libcurl/opts/Makefile.inc
index b21f32356..07547503b 100644
--- a/docs/libcurl/opts/Makefile.inc
+++ b/docs/libcurl/opts/Makefile.inc
@@ -83,6 +83,8 @@ man_MANS = \
CURLOPT_ACCEPTTIMEOUT_MS.3 \
CURLOPT_ACCEPT_ENCODING.3 \
CURLOPT_ADDRESS_SCOPE.3 \
+ CURLOPT_ALTSVC.3 \
+ CURLOPT_ALTSVC_CTRL.3 \
CURLOPT_APPEND.3 \
CURLOPT_AUTOREFERER.3 \
CURLOPT_BUFFERSIZE.3 \
diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions
index 934ece20c..0f43aee31 100644
--- a/docs/libcurl/symbols-in-versions
+++ b/docs/libcurl/symbols-in-versions
@@ -12,6 +12,12 @@
Name Introduced Deprecated Removed
+CURLALTSVC_ALTUSED 7.64.1
+CURLALTSVC_H1 7.64.1
+CURLALTSVC_H2 7.64.1
+CURLALTSVC_H3 7.64.1
+CURLALTSVC_IMMEDIATELY 7.64.1
+CURLALTSVC_READONLYFILE 7.64.1
CURLAUTH_ANY 7.10.6
CURLAUTH_ANYSAFE 7.10.6
CURLAUTH_BASIC 7.10.6
@@ -343,6 +349,8 @@ CURLOPT_ABSTRACT_UNIX_SOCKET 7.53.0
CURLOPT_ACCEPTTIMEOUT_MS 7.24.0
CURLOPT_ACCEPT_ENCODING 7.21.6
CURLOPT_ADDRESS_SCOPE 7.19.0
+CURLOPT_ALTSVC 7.64.1
+CURLOPT_ALTSVC_CTRL 7.64.1
CURLOPT_APPEND 7.17.0
CURLOPT_AUTOREFERER 7.1
CURLOPT_BUFFERSIZE 7.10
@@ -432,8 +440,6 @@ CURLOPT_HTTPREQUEST 7.1 - 7.15.5
CURLOPT_HTTP_CONTENT_DECODING 7.16.2
CURLOPT_HTTP_TRANSFER_DECODING 7.16.2
CURLOPT_HTTP_VERSION 7.9.1
-CURLOPT_TRAILERFUNCTION 7.64.0
-CURLOPT_TRAILERDATA 7.64.0
CURLOPT_IGNORE_CONTENT_LENGTH 7.14.1
CURLOPT_INFILE 7.1 7.9.7
CURLOPT_INFILESIZE 7.1
@@ -615,6 +621,8 @@ CURLOPT_TLS13_CIPHERS 7.61.0
CURLOPT_TLSAUTH_PASSWORD 7.21.4
CURLOPT_TLSAUTH_TYPE 7.21.4
CURLOPT_TLSAUTH_USERNAME 7.21.4
+CURLOPT_TRAILERDATA 7.64.0
+CURLOPT_TRAILERFUNCTION 7.64.0
CURLOPT_TRANSFERTEXT 7.1.1
CURLOPT_TRANSFER_ENCODING 7.21.6
CURLOPT_UNIX_SOCKET_PATH 7.40.0
@@ -855,8 +863,6 @@ CURL_PUSH_DENY 7.44.0
CURL_PUSH_OK 7.44.0
CURL_READFUNC_ABORT 7.12.1
CURL_READFUNC_PAUSE 7.18.0
-CURL_TRAILERFUNC_OK 7.64.0
-CURL_TRAILERFUNC_ABORT 7.64.0
CURL_REDIR_GET_ALL 7.19.1
CURL_REDIR_POST_301 7.19.1
CURL_REDIR_POST_302 7.19.1
@@ -903,7 +909,10 @@ CURL_TIMECOND_LASTMOD 7.9.7
CURL_TIMECOND_NONE 7.9.7
CURL_TLSAUTH_NONE 7.21.4
CURL_TLSAUTH_SRP 7.21.4
+CURL_TRAILERFUNC_ABORT 7.64.0
+CURL_TRAILERFUNC_OK 7.64.0
CURL_UPKEEP_INTERVAL_DEFAULT 7.62.0
+CURL_VERSION_ALTSVC 7.64.1
CURL_VERSION_ASYNCHDNS 7.10.7
CURL_VERSION_BROTLI 7.57.0
CURL_VERSION_CONV 7.15.4
diff --git a/include/curl/curl.h b/include/curl/curl.h
index 3e0eba725..86a24184a 100644
--- a/include/curl/curl.h
+++ b/include/curl/curl.h
@@ -881,6 +881,14 @@ typedef enum {
#define CURLHEADER_UNIFIED 0
#define CURLHEADER_SEPARATE (1<<0)
+/* CURLALTSVC_* are bits for the CURLOPT_ALTSVC_CTRL option */
+#define CURLALTSVC_IMMEDIATELY (1<<0)
+#define CURLALTSVC_ALTUSED (1<<1)
+#define CURLALTSVC_READONLYFILE (1<<2)
+#define CURLALTSVC_H1 (1<<3)
+#define CURLALTSVC_H2 (1<<4)
+#define CURLALTSVC_H3 (1<<5)
+
/* CURLPROTO_ defines are for the CURLOPT_*PROTOCOLS options */
#define CURLPROTO_HTTP (1<<0)
#define CURLPROTO_HTTPS (1<<1)
@@ -1904,6 +1912,12 @@ typedef enum {
/* set this to 1L to allow HTTP/0.9 responses or 0L to disallow */
CINIT(HTTP09_ALLOWED, LONG, 285),
+ /* alt-svc control bitmask */
+ CINIT(ALTSVC_CTRL, LONG, 286),
+
+ /* alt-svc cache file name to possibly read from/write to */
+ CINIT(ALTSVC, STRINGPOINT, 287),
+
CURLOPT_LASTENTRY /* the last unused */
} CURLoption;
@@ -2766,6 +2780,7 @@ typedef struct {
#define CURL_VERSION_HTTPS_PROXY (1<<21) /* HTTPS-proxy support built-in */
#define CURL_VERSION_MULTI_SSL (1<<22) /* Multiple SSL backends available */
#define CURL_VERSION_BROTLI (1<<23) /* Brotli features are present. */
+#define CURL_VERSION_ALTSVC (1<<24) /* Alt-Svc handling built-in */
/*
* NAME curl_version_info()
diff --git a/include/curl/typecheck-gcc.h b/include/curl/typecheck-gcc.h
index 01df7b15f..8018ea37f 100644
--- a/include/curl/typecheck-gcc.h
+++ b/include/curl/typecheck-gcc.h
@@ -7,7 +7,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
- * Copyright (C) 1998 - 2018, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2019, 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
@@ -256,6 +256,7 @@ _CURL_WARNING(_curl_easy_getinfo_err_curl_off_t,
#define _curl_is_string_option(option) \
((option) == CURLOPT_ABSTRACT_UNIX_SOCKET || \
(option) == CURLOPT_ACCEPT_ENCODING || \
+ (option) == CURLOPT_ALTSVC || \
(option) == CURLOPT_CAINFO || \
(option) == CURLOPT_CAPATH || \
(option) == CURLOPT_COOKIE || \
diff --git a/lib/Makefile.inc b/lib/Makefile.inc
index ce8b36eee..6c47bcda5 100644
--- a/lib/Makefile.inc
+++ b/lib/Makefile.inc
@@ -55,7 +55,7 @@ LIB_CFILES = file.c timeval.c base64.c hostip.c progress.c formdata.c \
curl_multibyte.c hostcheck.c conncache.c pipeline.c dotdot.c \
x509asn1.c http2.c smb.c curl_endian.c curl_des.c system_win32.c \
mime.c sha256.c setopt.c curl_path.c curl_ctype.c curl_range.c psl.c \
- doh.c urlapi.c
+ doh.c urlapi.c altsvc.c
LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \
formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h if2ip.h \
@@ -75,7 +75,8 @@ LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \
curl_setup_once.h multihandle.h setup-vms.h pipeline.h dotdot.h \
x509asn1.h http2.h sigpipe.h smb.h curl_endian.h curl_des.h \
curl_printf.h system_win32.h rand.h mime.h curl_sha256.h setopt.h \
- curl_path.h curl_ctype.h curl_range.h psl.h doh.h urlapi-int.h
+ curl_path.h curl_ctype.h curl_range.h psl.h doh.h urlapi-int.h \
+ altsvc.h
LIB_RCFILES = libcurl.rc
diff --git a/lib/altsvc.c b/lib/altsvc.c
new file mode 100644
index 000000000..97cdc7b8a
--- /dev/null
+++ b/lib/altsvc.c
@@ -0,0 +1,569 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2019, 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
+ * are also available at https://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+/*
+ * The Alt-Svc: header is defined in RFC 7838:
+ * https://tools.ietf.org/html/rfc7838
+ */
+#include "curl_setup.h"
+
+#if !defined(CURL_DISABLE_HTTP) && defined(USE_ALTSVC)
+#include <curl/curl.h>
+#include "urldata.h"
+#include "altsvc.h"
+#include "cookie.h" /* for Curl_get_line() */
+#include "strcase.h"
+#include "parsedate.h"
+#include "sendf.h"
+#include "warnless.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+#define MAX_ALTSVC_LINE 4095
+#define MAX_ALTSVC_DATELENSTR "64"
+#define MAX_ALTSVC_DATELEN 64
+#define MAX_ALTSVC_HOSTLENSTR "512"
+#define MAX_ALTSVC_HOSTLEN 512
+#define MAX_ALTSVC_ALPNLENSTR "10"
+#define MAX_ALTSVC_ALPNLEN 10
+
+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"))
+ return ALPN_h3;
+ return ALPN_none; /* unknown, probably rubbish input */
+}
+
+/* Given the ALPN ID, return the name */
+const char *Curl_alpnid2str(enum alpnid id)
+{
+ switch(id) {
+ case ALPN_h1:
+ return "h1";
+ case ALPN_h2:
+ return "h2";
+ case ALPN_h2c:
+ return "h2c";
+ case ALPN_h3:
+ return "h3";
+ default:
+ return ""; /* bad */
+ }
+}
+
+
+static void altsvc_free(struct altsvc *as)
+{
+ free(as->srchost);
+ free(as->dsthost);
+ free(as);
+}
+
+static struct altsvc *altsvc_createid(const char *srchost,
+ const char *dsthost,
+ enum alpnid srcalpnid,
+ enum alpnid dstalpnid,
+ unsigned int srcport,
+ unsigned int dstport)
+{
+ struct altsvc *as = calloc(sizeof(struct altsvc), 1);
+ if(!as)
+ return NULL;
+
+ as->srchost = strdup(srchost);
+ if(!as->srchost)
+ goto error;
+ as->dsthost = strdup(dsthost);
+ if(!as->dsthost)
+ goto error;
+
+ as->srcalpnid = srcalpnid;
+ as->dstalpnid = dstalpnid;
+ as->srcport = curlx_ultous(srcport);
+ as->dstport = curlx_ultous(dstport);
+
+ return as;
+ error:
+ altsvc_free(as);
+ return NULL;
+}
+
+static struct altsvc *altsvc_create(char *srchost,
+ char *dsthost,
+ char *srcalpn,
+ char *dstalpn,
+ unsigned int srcport,
+ unsigned int dstport)
+{
+ enum alpnid dstalpnid = alpn2alpnid(dstalpn);
+ enum alpnid srcalpnid = alpn2alpnid(srcalpn);
+ if(!srcalpnid || !dstalpnid)
+ return NULL;
+ return altsvc_createid(srchost, dsthost, srcalpnid, dstalpnid,
+ srcport, dstport);
+}
+
+/* only returns SERIOUS errors */
+static CURLcode altsvc_add(struct altsvcinfo *asi, char *line)
+{
+ /* Example line:
+ h2 example.com 443 h3 shiny.example.com 8443 "20191231 10:00:00" 1
+ */
+ char srchost[MAX_ALTSVC_HOSTLEN + 1];
+ char dsthost[MAX_ALTSVC_HOSTLEN + 1];
+ char srcalpn[MAX_ALTSVC_ALPNLEN + 1];
+ char dstalpn[MAX_ALTSVC_ALPNLEN + 1];
+ char date[MAX_ALTSVC_DATELEN + 1];
+ unsigned int srcport;
+ unsigned int dstport;
+ unsigned int prio;
+ unsigned int persist;
+ int rc;
+
+ rc = sscanf(line,
+ "%" MAX_ALTSVC_ALPNLENSTR "s %" MAX_ALTSVC_HOSTLENSTR "s %u "
+ "%" MAX_ALTSVC_ALPNLENSTR "s %" MAX_ALTSVC_HOSTLENSTR "s %u "
+ "\"%" MAX_ALTSVC_DATELENSTR "[^\"]\" %u %u",
+ srcalpn, srchost, &srcport,
+ dstalpn, dsthost, &dstport,
+ date, &persist, &prio);
+ if(9 == rc) {
+ struct altsvc *as;
+ time_t expires = curl_getdate(date, NULL);
+ as = altsvc_create(srchost, dsthost, srcalpn, dstalpn, srcport, dstport);
+ if(as) {
+ as->expires = expires;
+ as->prio = prio;
+ as->persist = persist ? 1 : 0;
+ Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node);
+ asi->num++; /* one more entry */
+ }
+ }
+
+ return CURLE_OK;
+}
+
+/*
+ * Load alt-svc entries from the given file. The text based line-oriented file
+ * format is documented here:
+ * https://github.com/curl/curl/wiki/QUIC-implementation
+ *
+ * This function only returns error on major problems that prevents alt-svc
+ * handling to work completely. It will ignore individual syntactical errors
+ * etc.
+ */
+static CURLcode altsvc_load(struct altsvcinfo *asi, const char *file)
+{
+ CURLcode result = CURLE_OK;
+ char *line = NULL;
+ FILE *fp = fopen(file, FOPEN_READTEXT);
+ if(fp) {
+ line = malloc(MAX_ALTSVC_LINE);
+ if(!line)
+ goto fail;
+ while(Curl_get_line(line, MAX_ALTSVC_LINE, fp)) {
+ char *lineptr = line;
+ while(*lineptr && ISBLANK(*lineptr))
+ lineptr++;
+ if(*lineptr == '#')
+ /* skip commented lines */
+ continue;
+
+ altsvc_add(asi, lineptr);
+ }
+ free(line); /* free the line buffer */
+ fclose(fp);
+ }
+ return result;
+
+ fail:
+ free(line);
+ fclose(fp);
+ return CURLE_OUT_OF_MEMORY;
+}
+
+/*
+ * Write this single altsvc entry to a single output line
+ */
+
+static CURLcode altsvc_out(struct altsvc *as, FILE *fp)
+{
+ struct tm stamp;
+ Curl_gmtime(as->expires, &stamp);
+
+ fprintf(fp,
+ "%s %s %u "
+ "%s %s %u "
+ "\"%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,
+ stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday,
+ stamp.tm_hour, stamp.tm_min, stamp.tm_sec,
+ as->persist, as->prio);
+ return CURLE_OK;
+}
+
+/* ---- library-wide functions below ---- */
+
+/*
+ * Curl_altsvc_init() creates a new altsvc cache.
+ * It returns the new instance or NULL if something goes wrong.
+ */
+struct altsvcinfo *Curl_altsvc_init(void)
+{
+ struct altsvcinfo *asi = calloc(sizeof(struct altsvcinfo), 1);
+ if(!asi)
+ return NULL;
+ Curl_llist_init(&asi->list, NULL);
+
+ /* set default behavior */
+ asi->flags = CURLALTSVC_H1
+#ifdef USE_NGHTTP2
+ | CURLALTSVC_H2
+#endif
+#ifdef USE_HTTP3
+ /* TODO: adjust when known */
+ | CURLALTSVC_H3
+#endif
+ ;
+ return asi;
+}
+
+/*
+ * Curl_altsvc_load() loads alt-svc from file.
+ */
+CURLcode Curl_altsvc_load(struct altsvcinfo *asi, const char *file)
+{
+ CURLcode result;
+ DEBUGASSERT(asi);
+ result = altsvc_load(asi, file);
+ return result;
+}
+
+/*
+ * Curl_altsvc_ctrl() passes on the external bitmask.
+ */
+CURLcode Curl_altsvc_ctrl(struct altsvcinfo *asi, const long ctrl)
+{
+ DEBUGASSERT(asi);
+ if(!ctrl)
+ /* unexpected */
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ asi->flags = ctrl;
+ return CURLE_OK;
+}
+
+/*
+ * Curl_altsvc_cleanup() frees an altsvc cache instance and all associated
+ * resources.
+ */
+void Curl_altsvc_cleanup(struct altsvcinfo *altsvc)
+{
+ struct curl_llist_element *e;
+ struct curl_llist_element *n;
+ if(altsvc) {
+ for(e = altsvc->list.head; e; e = n) {
+ struct altsvc *as = e->ptr;
+ n = e->next;
+ altsvc_free(as);
+ }
+ free(altsvc);
+ }
+}
+
+/*
+ * Curl_altsvc_save() writes the altsvc cache to a file.
+ */
+CURLcode Curl_altsvc_save(struct altsvcinfo *altsvc, const char *file)
+{
+ struct curl_llist_element *e;
+ struct curl_llist_element *n;
+ CURLcode result = CURLE_OK;
+ FILE *out;
+
+ if(!altsvc)
+ /* no cache activated */
+ return CURLE_OK;
+
+ if((altsvc->flags & CURLALTSVC_READONLYFILE) || !file[0])
+ /* marked as read-only or zero length file name */
+ return CURLE_OK;
+ out = fopen(file, 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);
+ if(result)
+ break;
+ }
+ fclose(out);
+ return result;
+}
+
+static CURLcode getalnum(const char **ptr, char *alpnbuf, size_t buflen)
+{
+ size_t len;
+ const char *protop;
+ const char *p = *ptr;
+ while(*p && ISBLANK(*p))
+ p++;
+ protop = p;
+ while(*p && ISALNUM(*p))
+ p++;
+ len = p - protop;
+
+ if(!len || (len >= buflen))
+ return CURLE_BAD_FUNCTION_ARGUMENT; /* TODO: improve error code */
+ memcpy(alpnbuf, protop, len);
+ alpnbuf[len] = 0;
+ *ptr = p;
+ return CURLE_OK;
+}
+
+/* altsvc_flush() removes all alternatives for this source origin from the
+ list */
+static void altsvc_flush(struct altsvcinfo *asi, enum alpnid srcalpnid,
+ const char *srchost, unsigned short srcport)
+{
+ struct curl_llist_element *e;
+ struct curl_llist_element *n;
+ 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)) {
+ Curl_llist_remove(&asi->list, e, NULL);
+ altsvc_free(as);
+ asi->num--;
+ }
+ }
+}
+
+#ifdef DEBUGBUILD
+/* to play well with debug builds, we can *set* a fixed time this will
+ return */
+static time_t debugtime(void *unused)
+{
+ char *timestr = getenv("CURL_TIME");
+ (void)unused;
+ if(timestr) {
+ unsigned long val = strtol(timestr, NULL, 10);
+ return (time_t)val;
+ }
+ return time(NULL);
+}
+#define time(x) debugtime(x)
+#endif
+
+/*
+ * Curl_altsvc_parse() takes an incoming alt-svc response header and stores
+ * the data correctly in the cache.
+ *
+ * 'value' points to the header *value*. That's contents to the right of the
+ * header name.
+ */
+CURLcode Curl_altsvc_parse(struct Curl_easy *data,
+ struct altsvcinfo *asi, const char *value,
+ enum alpnid srcalpnid, const char *srchost,
+ unsigned short srcport)
+{
+ const char *p = value;
+ size_t len;
+ enum alpnid dstalpnid = srcalpnid; /* the same by default */
+ char namebuf[MAX_ALTSVC_HOSTLEN] = "";
+ 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;
+
+ DEBUGASSERT(asi);
+
+ /* Flush all cached alternatives for this source origin, if any */
+ altsvc_flush(asi, srcalpnid, srchost, srcport);
+
+ /* "clear" is a magic keyword */
+ if(strcasecompare(alpnbuf, "clear")) {
+ /* TODO: clear whatever it is it should clear */
+ 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;
+ p++;
+ if(*p != ':') {
+ /* host name starts here */
+ const char *hostp = p;
+ while(*p && (ISALNUM(*p) || (*p == '.') || (*p == '-')))
+ p++;
+ len = p - hostp;
+ if(!len || (len >= MAX_ALTSVC_HOSTLEN))
+ return CURLE_BAD_FUNCTION_ARGUMENT; /* TODO: improve error code */
+ memcpy(namebuf, hostp, len);
+ namebuf[len] = 0;
+ dsthost = namebuf;
+ }
+ else {
+ /* no destination name, use source host */
+ dsthost = srchost;
+ }
+ 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;
+ }
+ 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) {
+ /* TODO: 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));
+ }
+ }
+ /* after the double quote there can be a comma if there's another
+ string or a semicolon if no more */
+ if(*p == ',') {
+ /* comma means another alternative is presented */
+ p++;
+ result = getalnum(&p, alpnbuf, sizeof(alpnbuf));
+ if(result)
+ /* failed to parse, but since we alredy did at least one host we
+ return OK */
+ return CURLE_OK;
+ }
+ }
+ } while(*p && (*p != ';') && (*p != '\n') && (*p != '\r'));
+
+ return CURLE_OK;
+}
+
+/*
+ * Return TRUE on a match
+ */
+bool Curl_altsvc_lookup(struct altsvcinfo *asi,
+ enum alpnid srcalpnid, const char *srchost,
+ int srcport,
+ enum alpnid *dstalpnid, const char **dsthost,
+ int *dstport)
+{
+ struct curl_llist_element *e;
+ struct curl_llist_element *n;
+ time_t now = time(NULL);
+ DEBUGASSERT(asi);
+ DEBUGASSERT(srchost);
+ DEBUGASSERT(dsthost);
+
+ for(e = asi->list.head; e; e = n) {
+ struct altsvc *as = e->ptr;
+ n = e->next;
+ if(as->expires < now) {
+ /* an expired entry, remove */
+ altsvc_free(as);
+ continue;
+ }
+ if((as->srcalpnid == srcalpnid) &&
+ strcasecompare(as->srchost, srchost) &&
+ as->srcport == srcport) {
+ /* match */
+ *dstalpnid = as->dstalpnid;
+ *dsthost = as->dsthost;
+ *dstport = as->dstport;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+#endif /* CURL_DISABLE_HTTP || USE_ALTSVC */
diff --git a/lib/altsvc.h b/lib/altsvc.h
new file mode 100644
index 000000000..eefb45bf6
--- /dev/null
+++ b/lib/altsvc.h
@@ -0,0 +1,77 @@
+#ifndef HEADER_CURL_ALTSVC_H
+#define HEADER_CURL_ALTSVC_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2019, 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
+ * are also available at https://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#if !defined(CURL_DISABLE_HTTP) && defined(USE_ALTSVC)
+#include <curl/curl.h>
+#include "llist.h"
+
+enum alpnid {
+ ALPN_none,
+ ALPN_h1,
+ ALPN_h2,
+ ALPN_h2c,
+ ALPN_h3
+};
+
+struct altsvc {
+ char *srchost;
+ char *dsthost;
+ unsigned short srcport;
+ unsigned short dstport;
+ enum alpnid srcalpnid;
+ enum alpnid dstalpnid;
+ time_t expires;
+ bool persist;
+ int prio;
+ struct curl_llist_element node;
+};
+
+struct altsvcinfo {
+ char *filename;
+ struct curl_llist list; /* list of entries */
+ size_t num; /* number of alt-svc entries */
+ long flags; /* the publicly set bitmask */
+};
+
+const char *Curl_alpnid2str(enum alpnid id);
+struct altsvcinfo *Curl_altsvc_init(void);
+CURLcode Curl_altsvc_load(struct altsvcinfo *asi, const char *file);
+CURLcode Curl_altsvc_save(struct altsvcinfo *asi, const char *file);
+CURLcode Curl_altsvc_ctrl(struct altsvcinfo *asi, const long ctrl);
+void Curl_altsvc_cleanup(struct altsvcinfo *altsvc);
+CURLcode Curl_altsvc_parse(struct Curl_easy *data,
+ struct altsvcinfo *altsvc, const char *value,
+ enum alpnid srcalpn, const char *srchost,
+ unsigned short srcport);
+bool Curl_altsvc_lookup(struct altsvcinfo *asi,
+ enum alpnid srcalpnid, const char *srchost,
+ int srcport,
+ enum alpnid *dstalpnid, const char **dsthost,
+ int *dstport);
+#else
+/* disabled */
+#define Curl_altsvc_save(a,b)
+#endif /* CURL_DISABLE_HTTP || USE_ALTSVC */
+#endif /* HEADER_CURL_ALTSVC_H */
diff --git a/lib/cookie.c b/lib/cookie.c
index 65cc11732..b24aaf718 100644
--- a/lib/cookie.c
+++ b/lib/cookie.c
@@ -1092,7 +1092,7 @@ Curl_cookie_add(struct Curl_easy *data,
* get_line() makes sure to only return complete whole lines that fit in 'len'
* bytes and end with a newline.
*/
-static char *get_line(char *buf, int len, FILE *input)
+char *Curl_get_line(char *buf, int len, FILE *input)
{
bool partial = FALSE;
while(1) {
@@ -1172,7 +1172,7 @@ struct CookieInfo *Curl_cookie_init(struct Curl_easy *data,
line = malloc(MAX_COOKIE_LINE);
if(!line)
goto fail;
- while(get_line(line, MAX_COOKIE_LINE, fp)) {
+ while(Curl_get_line(line, MAX_COOKIE_LINE, fp)) {
if(checkprefix("Set-Cookie:", line)) {
/* This is a cookie line, get it! */
lineptr = &line[11];
diff --git a/lib/cookie.h b/lib/cookie.h
index b2730cfb9..6ac4a6ac0 100644
--- a/lib/cookie.h
+++ b/lib/cookie.h
@@ -101,6 +101,7 @@ struct Cookie *Curl_cookie_getlist(struct CookieInfo *, const char *,
void Curl_cookie_freelist(struct Cookie *cookies);
void Curl_cookie_clearall(struct CookieInfo *cookies);
void Curl_cookie_clearsess(struct CookieInfo *cookies);
+char *Curl_get_line(char *buf, int len, FILE *input);
#if defined(CURL_DISABLE_HTTP) || defined(CURL_DISABLE_COOKIES)
#define Curl_cookie_list(x) NULL
diff --git a/lib/http.c b/lib/http.c
index d0a01979d..f5709b68b 100644
--- a/lib/http.c
+++ b/lib/http.c
@@ -77,6 +77,7 @@
#include "http2.h"
#include "connect.h"
#include "strdup.h"
+#include "altsvc.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@@ -3978,6 +3979,20 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
}
}
}
+#ifdef USE_ALTSVC
+ /* If enabled, the header is incoming and this is over HTTPS */
+ else if(data->asi && checkprefix("Alt-Svc:", k->p) &&
+ (conn->handler->flags & PROTOPT_SSL)) {
+ /* the ALPN of the current request */
+ enum alpnid id = (conn->httpversion == 20) ? ALPN_h2 : ALPN_h1;
+ result = Curl_altsvc_parse(data, data->asi,
+ &k->p[ strlen("Alt-Svc:") ],
+ id, conn->host.name,
+ curlx_uitous(conn->remote_port));
+ if(result)
+ return result;
+ }
+#endif
else if(conn->handler->protocol & CURLPROTO_RTSP) {
result = Curl_rtsp_parseheader(conn, k->p);
if(result)
diff --git a/lib/setopt.c b/lib/setopt.c
index fdcbfacae..b5f74a93d 100644
--- a/lib/setopt.c
+++ b/lib/setopt.c
@@ -44,6 +44,7 @@
#include "http2.h"
#include "setopt.h"
#include "multiif.h"
+#include "altsvc.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@@ -2655,6 +2656,31 @@ static CURLcode vsetopt(struct Curl_easy *data, CURLoption option,
data->set.trailer_data = va_arg(param, void *);
#endif
break;
+#ifdef USE_ALTSVC
+ case CURLOPT_ALTSVC:
+ if(!data->asi) {
+ data->asi = Curl_altsvc_init();
+ if(!data->asi)
+ return CURLE_OUT_OF_MEMORY;
+ }
+ argptr = va_arg(param, char *);
+ result = Curl_setstropt(&data->set.str[STRING_ALTSVC], argptr);
+ if(result)
+ return result;
+ (void)Curl_altsvc_load(data->asi, argptr);
+ break;
+ case CURLOPT_ALTSVC_CTRL:
+ if(!data->asi) {
+ data->asi = Curl_altsvc_init();
+ if(!data->asi)
+ return CURLE_OUT_OF_MEMORY;
+ }
+ arg = va_arg(param, long);
+ result = Curl_altsvc_ctrl(data->asi, arg);
+ if(result)
+ return result;
+ break;
+#endif
default:
/* unknown tag and its companion, just ignore: */
result = CURLE_UNKNOWN_OPTION;
diff --git a/lib/url.c b/lib/url.c
index 3b4ff3ee7..bc00bfae6 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -120,6 +120,7 @@ bool curl_win32_idn_to_ascii(const char *in, char **out);
#include "dotdot.h"
#include "strdup.h"
#include "setopt.h"
+#include "altsvc.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@@ -374,6 +375,11 @@ CURLcode Curl_close(struct Curl_easy *data)
Curl_safefree(data->state.headerbuff);
Curl_safefree(data->state.ulbuf);
Curl_flush_cookies(data, 1);
+#ifdef USE_ALTSVC
+ Curl_altsvc_save(data->asi, data->set.str[STRING_ALTSVC]);
+ Curl_altsvc_cleanup(data->asi);
+ data->asi = NULL;
+#endif
Curl_digest_cleanup(data);
Curl_safefree(data->info.contenttype);
Curl_safefree(data->info.wouldredirect);
@@ -3368,6 +3374,34 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data,
conn_to_host = conn_to_host->next;
}
+#ifdef USE_ALTSVC
+ if(data->asi && !host && (port == -1) &&
+ (conn->handler->protocol == CURLPROTO_HTTPS)) {
+ /* no connect_to match, try alt-svc! */
+ const char *nhost;
+ int nport;
+ enum alpnid nalpnid;
+ bool hit;
+ host = conn->host.rawalloc;
+ hit = Curl_altsvc_lookup(data->asi,
+ ALPN_h1, host, conn->remote_port, /* from */
+ &nalpnid, &nhost, &nport /* to */);
+ if(hit) {
+ char *hostd = strdup((char *)nhost);
+ if(!hostd)
+ return CURLE_OUT_OF_MEMORY;
+ conn->conn_to_host.rawalloc = hostd;
+ conn->conn_to_host.name = hostd;
+ conn->bits.conn_to_host = TRUE;
+ conn->conn_to_port = nport;
+ conn->bits.conn_to_port = TRUE;
+ infof(data, "Alt-svc connecting from [%s]%s:%d to [%s]%s:%d\n",
+ Curl_alpnid2str(ALPN_h1), host, conn->remote_port,
+ Curl_alpnid2str(nalpnid), hostd, nport);
+ }
+ }
+#endif
+
return result;
}
diff --git a/lib/urldata.h b/lib/urldata.h
index f3b8d3373..e5596b87f 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -1478,6 +1478,9 @@ enum dupstring {
#endif
STRING_TARGET, /* CURLOPT_REQUEST_TARGET */
STRING_DOH, /* CURLOPT_DOH_URL */
+#ifdef USE_ALTSVC
+ STRING_ALTSVC, /* CURLOPT_ALTSVC */
+#endif
/* -- end of zero-terminated strings -- */
STRING_LASTZEROTERMINATED,
@@ -1794,6 +1797,9 @@ struct Curl_easy {
NOTE that the 'cookie' field in the
UserDefined struct defines if the "engine"
is to be used or not. */
+#ifdef USE_ALTSVC
+ struct altsvcinfo *asi; /* the alt-svc cache */
+#endif
struct Progress progress; /* for all the progress meter data */
struct UrlState state; /* struct for fields used for state info and
other dynamic purposes */
diff --git a/lib/version.c b/lib/version.c
index c1be9ab6d..9369ae8e3 100644
--- a/lib/version.c
+++ b/lib/version.c
@@ -370,6 +370,9 @@ static curl_version_info_data version_info = {
#if defined(HAVE_BROTLI)
| CURL_VERSION_BROTLI
#endif
+#if defined(USE_ALTSVC)
+ | CURL_VERSION_ALTSVC
+#endif
,
NULL, /* ssl_version */
0, /* ssl_version_num, this is kept at zero */
diff --git a/src/tool_help.c b/src/tool_help.c
index 414e00b21..0f6bcd36b 100644
--- a/src/tool_help.c
+++ b/src/tool_help.c
@@ -525,6 +525,7 @@ static const struct feat feats[] = {
{"HTTPS-proxy", CURL_VERSION_HTTPS_PROXY},
{"MultiSSL", CURL_VERSION_MULTI_SSL},
{"PSL", CURL_VERSION_PSL},
+ {"alt-svc", CURL_VERSION_ALTSVC},
};
void tool_help(void)
diff --git a/tests/FILEFORMAT b/tests/FILEFORMAT
index 505c573cb..85e731966 100644
--- a/tests/FILEFORMAT
+++ b/tests/FILEFORMAT
@@ -249,6 +249,7 @@ unittest
unix-sockets
WinSSL
ld_preload
+alt-svc
as well as each protocol that curl supports. A protocol only needs to be
specified if it is different from the server (useful when the server
diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc
index 0dcbedfe5..bd24b4ba2 100644
--- a/tests/data/Makefile.inc
+++ b/tests/data/Makefile.inc
@@ -183,7 +183,7 @@ test1590 test1591 test1592 \
test1600 test1601 test1602 test1603 test1604 test1605 test1606 test1607 \
test1608 test1609 test1620 test1621 \
\
-test1650 test1651 test1652 test1653 \
+test1650 test1651 test1652 test1653 test1654 \
\
test1700 test1701 test1702 \
\
diff --git a/tests/data/test1654 b/tests/data/test1654
new file mode 100644
index 000000000..cae49d663
--- /dev/null
+++ b/tests/data/test1654
@@ -0,0 +1,57 @@
+<testcase>
+<info>
+<keywords>
+unittest
+alt-svc
+altsvc
+</keywords>
+</info>
+
+<client>
+<server>
+none
+</server>
+<features>
+unittest
+alt-svc
+</features>
+
+# This date is exactly "20190124 22:34:21" UTC
+<setenv>
+CURL_TIME=1548369261
+</setenv>
+<name>
+alt-svc
+</name>
+<command>
+log/1654
+</command>
+<tool>
+unit1654
+</tool>
+<file name="log/1654" mode="text">
+h2 example.com 443 h3 shiny.example.com 8443 "20191231 00:00:00" 0 1
+# a comment
+h2c example.com 443 h3 shiny.example.com 8443 "20291231 23:30:00" 0 1
+ h1 example.com 443 h3 shiny.example.com 8443 "20121231 00:00:01" 0 1
+ h3 example.com 443 h3 shiny.example.com 8443 "20131231 00:00:00" 0 1
+ # also a comment
+bad example.com 443 h3 shiny.example.com 8443 "20191231 00:00:00" 0 1
+rubbish
+</file>
+</client>
+<verify>
+<file name="log/1654-out" mode="text">
+# Your alt-svc cache. https://curl.haxx.se/docs/alt-svc.html
+# This file was generated by libcurl! Edit at your own risk.
+h2 example.com 443 h3 shiny.example.com 8443 "20191231 00:00:00" 0 1
+h2c example.com 443 h3 shiny.example.com 8443 "20291231 23:30:00" 0 1
+h1 example.com 443 h3 shiny.example.com 8443 "20121231 00:00:01" 0 1
+h3 example.com 443 h3 shiny.example.com 8443 "20131231 00:00:00" 0 1
+h1 example.org 8080 h2 example.com 8080 "20190125 22:34:21" 0 0
+h1 2.example.org 8080 h3 2.example.org 8080 "20190125 22:34:21" 0 0
+h1 3.example.org 8080 h2 example.com 8080 "20190125 22:34:21" 0 0
+h1 3.example.org 8080 h3 yesyes.com 8080 "20190125 22:34:21" 0 0
+h2c example.org 80 h2 example.com 443 "20190124 22:36:21" 0 0
+</verify>
+</testcase>
diff --git a/tests/runtests.pl b/tests/runtests.pl
index 5a1d1b6cd..1fb7354ec 100755
--- a/tests/runtests.pl
+++ b/tests/runtests.pl
@@ -233,6 +233,7 @@ my $has_crypto; # set if libcurl is built with cryptographic support
my $has_cares; # set if built with c-ares
my $has_threadedres;# set if built with threaded resolver
my $has_psl; # set if libcurl is built with PSL support
+my $has_altsvc; # set if libcurl is built with alt-svc support
my $has_ldpreload; # set if curl is built for systems supporting LD_PRELOAD
my $has_multissl; # set if curl is build with MultiSSL support
my $has_manual; # set if curl is built with built-in manual
@@ -2838,6 +2839,10 @@ sub checksystem {
# PSL enabled
$has_psl=1;
}
+ if($feat =~ /alt-svc/i) {
+ # alt-svc enabled
+ $has_altsvc=1;
+ }
if($feat =~ /AsynchDNS/i) {
if(!$has_cares) {
# this means threaded resolver
@@ -3387,6 +3392,11 @@ sub singletest {
next;
}
}
+ elsif($1 eq "alt-svc") {
+ if($has_altsvc) {
+ next;
+ }
+ }
elsif($1 eq "manual") {
if($has_manual) {
next;
diff --git a/tests/unit/Makefile.inc b/tests/unit/Makefile.inc
index 82eaec797..f3cba1c2a 100644
--- a/tests/unit/Makefile.inc
+++ b/tests/unit/Makefile.inc
@@ -11,7 +11,7 @@ UNITPROGS = unit1300 unit1301 unit1302 unit1303 unit1304 unit1305 unit1307 \
unit1399 \
unit1600 unit1601 unit1602 unit1603 unit1604 unit1605 unit1606 unit1607 \
unit1608 unit1609 unit1620 unit1621 \
- unit1650 unit1651 unit1652 unit1653
+ unit1650 unit1651 unit1652 unit1653 unit1654
unit1300_SOURCES = unit1300.c $(UNITFILES)
unit1300_CPPFLAGS = $(AM_CPPFLAGS)
@@ -115,3 +115,6 @@ unit1652_CPPFLAGS = $(AM_CPPFLAGS)
unit1653_SOURCES = unit1653.c $(UNITFILES)
unit1653_CPPFLAGS = $(AM_CPPFLAGS)
+
+unit1654_SOURCES = unit1654.c $(UNITFILES)
+unit1654_CPPFLAGS = $(AM_CPPFLAGS)
diff --git a/tests/unit/unit1654.c b/tests/unit/unit1654.c
new file mode 100644
index 000000000..7532c6d61
--- /dev/null
+++ b/tests/unit/unit1654.c
@@ -0,0 +1,124 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2019, 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
+ * are also available at https://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+#include "curlcheck.h"
+
+#include "urldata.h"
+#include "altsvc.h"
+
+static CURLcode
+unit_setup(void)
+{
+ return CURLE_OK;
+}
+
+static void
+unit_stop(void)
+{
+ curl_global_cleanup();
+}
+
+#if defined(CURL_DISABLE_HTTP) || !defined(USE_ALTSVC)
+UNITTEST_START
+{
+ return 0; /* nothing to do when HTTP is disabled or alt-svc support is
+ missing */
+}
+UNITTEST_STOP
+#else
+UNITTEST_START
+{
+ char outname[256];
+ CURL *curl;
+ CURLcode result;
+ struct altsvcinfo *asi = Curl_altsvc_init();
+ if(!asi)
+ return 1;
+ result = Curl_altsvc_load(asi, arg);
+ if(result)
+ return result;
+ curl = curl_easy_init();
+ if(!curl)
+ goto fail;
+ fail_unless(asi->num == 4, "wrong number of entries");
+ msnprintf(outname, sizeof(outname), "%s-out", arg);
+
+ result = Curl_altsvc_parse(curl, asi, "h2=\"example.com:8080\"",
+ ALPN_h1, "example.org", 8080);
+ if(result) {
+ fprintf(stderr, "Curl_altsvc_parse() failed!\n");
+ unitfail++;
+ }
+ fail_unless(asi->num == 5, "wrong number of entries");
+
+ result = Curl_altsvc_parse(curl, asi, "h3=\":8080\"",
+ ALPN_h1, "2.example.org", 8080);
+ if(result) {
+ fprintf(stderr, "Curl_altsvc_parse(2) failed!\n");
+ unitfail++;
+ }
+ fail_unless(asi->num == 6, "wrong number of entries");
+
+ result = Curl_altsvc_parse(curl, asi,
+ "h2=\"example.com:8080\", h3=\"yesyes.com\"",
+ ALPN_h1, "3.example.org", 8080);
+ if(result) {
+ fprintf(stderr, "Curl_altsvc_parse(3) failed!\n");
+ unitfail++;
+ }
+ /* that one should make two entries */
+ fail_unless(asi->num == 8, "wrong number of entries");
+
+ result = Curl_altsvc_parse(curl, asi, "h2=\"example.com:443\"; ma = 120;",
+ ALPN_h2c, "example.org", 80);
+ if(result) {
+ fprintf(stderr, "Curl_altsvc_parse(4) failed!\n");
+ unitfail++;
+ }
+ fail_unless(asi->num == 9, "wrong number of entries");
+
+ result = Curl_altsvc_parse(curl, asi,
+ "h2=\":443\", h3=\":443\"; ma = 120; persist = 1",
+ ALPN_h1, "curl.haxx.se", 80);
+ if(result) {
+ fprintf(stderr, "Curl_altsvc_parse(5) failed!\n");
+ unitfail++;
+ }
+ fail_unless(asi->num == 11, "wrong number of entries");
+
+ /* clear that one again and decrease the counter */
+ result = Curl_altsvc_parse(curl, asi, "clear;",
+ ALPN_h1, "curl.haxx.se", 80);
+ if(result) {
+ fprintf(stderr, "Curl_altsvc_parse(6) failed!\n");
+ unitfail++;
+ }
+ fail_unless(asi->num == 9, "wrong number of entries");
+
+ Curl_altsvc_save(asi, outname);
+
+ curl_easy_cleanup(curl);
+ fail:
+ Curl_altsvc_cleanup(asi);
+ return unitfail;
+}
+UNITTEST_STOP
+#endif