]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
alt-svc: the libcurl bits
authorDaniel Stenberg <daniel@haxx.se>
Sun, 3 Mar 2019 10:17:52 +0000 (11:17 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Sun, 3 Mar 2019 10:17:52 +0000 (11:17 +0100)
25 files changed:
configure.ac
docs/libcurl/curl_easy_setopt.3
docs/libcurl/opts/CURLOPT_ALTSVC.3 [new file with mode: 0644]
docs/libcurl/opts/CURLOPT_ALTSVC_CTRL.3 [new file with mode: 0644]
docs/libcurl/opts/Makefile.inc
docs/libcurl/symbols-in-versions
include/curl/curl.h
include/curl/typecheck-gcc.h
lib/Makefile.inc
lib/altsvc.c [new file with mode: 0644]
lib/altsvc.h [new file with mode: 0644]
lib/cookie.c
lib/cookie.h
lib/http.c
lib/setopt.c
lib/url.c
lib/urldata.h
lib/version.c
src/tool_help.c
tests/FILEFORMAT
tests/data/Makefile.inc
tests/data/test1654 [new file with mode: 0644]
tests/runtests.pl
tests/unit/Makefile.inc
tests/unit/unit1654.c [new file with mode: 0644]

index bb1a68508a67991c8119df2f679fcda897774072..3707a886e3b2808900a8b45aec31577afef2e98f 100755 (executable)
@@ -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
index 6d63912d76e6dcb73a102ebf1458e8cbe385829b..fc361d80c47f0a27d97d6d88fb6317186980d08e 100644 (file)
@@ -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 (file)
index 0000000..d6b5d87
--- /dev/null
@@ -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 (file)
index 0000000..bdbb382
--- /dev/null
@@ -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), "
index b21f32356c79338cb96a2e69d40453cc15653745..07547503b9ea7907445c0a1eba96c92ed4f096b1 100644 (file)
@@ -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                          \
index 934ece20c0f8d8ccef72e011bfdc369425227e13..0f43aee31c7b40fc063a2d83f09ce784a84f3cd7 100644 (file)
 
  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
index 3e0eba725db6318b21013bd1898cfb6cad269e3b..86a24184aa0838757e57c6c6cb80017c197c9512 100644 (file)
@@ -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()
index 01df7b15f9ab597793987609f9a8751fb236b0bc..8018ea37fe46e6e3fa58fc6fb8b14d05f4d1e9aa 100644 (file)
@@ -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 ||                                              \
index ce8b36eee0a19d5fc71d961f875f4a5e9c414569..6c47bcda55d9aed3f6c7d6bf16b79e35bc129b7b 100644 (file)
@@ -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 (file)
index 0000000..97cdc7b
--- /dev/null
@@ -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 (file)
index 0000000..eefb45b
--- /dev/null
@@ -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 */
index 65cc117327b0a717b8ed25b2729286e97eb1da99..b24aaf718c5435274d8bc8cc34dd4a83de8ab826 100644 (file)
@@ -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];
index b2730cfb91d92bea4d281879a29bc84aa4634dd9..6ac4a6ac096b5439cb3ab3112b0c6b586cb74b65 100644 (file)
@@ -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
index d0a01979df2d61fac8748321fb28b19ef4658197..f5709b68b47f621d5d365be1925cce3bb89a333b 100644 (file)
@@ -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)
index fdcbfacaef922b7cb5100fe1cf94a7a311d43236..b5f74a93dbe0cde11b9153c3dfe41df96ed0eb28 100644 (file)
@@ -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;
index 3b4ff3ee7523db2592cbf359841a9ae402d4b8b7..bc00bfae6a02a090c987aab4b4f2670d96dcf0e4 100644 (file)
--- 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;
 }
 
index f3b8d337315086466220c0d26fe7e4bf02880678..e5596b87f0947d64dc7e1834955daee74058d275 100644 (file)
@@ -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 */
index c1be9ab6db354cc3afc3f92b22e336318aa332c9..9369ae8e3f57db8a523d6baf7f71bc201efa640b 100644 (file)
@@ -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 */
index 414e00b212815ec44aba24fd23aea34c54250392..0f6bcd36bb606cd35934d465eefdfb76571e82dd 100644 (file)
@@ -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)
index 505c573cb14f90395154160a1a8d6c3041040c6c..85e73196691f66be6d4693eb76d659f774136acb 100644 (file)
@@ -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
index 0dcbedfe5c2bad0dfda38cb212f910cd42bc32c6..bd24b4ba20ce3ab8941f21a30a23325d2f50e0d3 100644 (file)
@@ -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 (file)
index 0000000..cae49d6
--- /dev/null
@@ -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>
index 5a1d1b6cd6cafc9a4a5d0e4ad40a2b5a4646874c..1fb7354ec7c7de1bf457270299dd6bda6c555091 100755 (executable)
@@ -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;
index 82eaec797593101ae3f9ae9279e305fd90b59677..f3cba1c2abb769f87db83134f55aa0fa25a9af1f 100644 (file)
@@ -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 (file)
index 0000000..7532c6d
--- /dev/null
@@ -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