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 @@ -4062,6 +4062,32 @@ AC_HELP_STRING([--disable-cookies],[Disable cookies support]), AC_MSG_RESULT(yes) ) +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 @@ -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, , et al. +.\" * Copyright (C) 1998 - 2019, Daniel Stenberg, , 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, , 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 + +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, , 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 + +#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, , et al. + * Copyright (C) 1998 - 2019, Daniel Stenberg, , 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, , 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 +#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, , 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 +#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 @@ -369,6 +369,9 @@ static curl_version_info_data version_info = { #endif #if defined(HAVE_BROTLI) | CURL_VERSION_BROTLI +#endif +#if defined(USE_ALTSVC) + | CURL_VERSION_ALTSVC #endif , NULL, /* ssl_version */ 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 @@ + + + +unittest +alt-svc +altsvc + + + + + +none + + +unittest +alt-svc + + +# This date is exactly "20190124 22:34:21" UTC + +CURL_TIME=1548369261 + + +alt-svc + + +log/1654 + + +unit1654 + + +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 + + + + +# 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 + + 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, , 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